예제 #1
0
    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")
예제 #2
0
    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
예제 #3
0
    def effectExtrema(self, forHorizontal=True, **kwArgs):
        """
        Returns a dict, indexed by glyph, of pairs of values. If
        forHorizontal is True, these values will be the yMaxDelta and
        yMinDelta; if False, they will be the xMaxDelta and xMinDelta. These
        values can then be used to test against the font's ascent/descent
        values in order to show VDMA-like output, or to be accumulated across
        all the features that are performed for a given script and lang/sys.
        
        Note that either or both of these values may be None; this can arise
        for cases like mark-to-mark, where potentially infinite stacking of
        marks can occur.
        
        The following keyword arguments are used:
            
            cache               A dict mapping object IDs to result dicts.
                                This is used during processing to speed up
                                analysis of deeply nested subtables, so the
                                effectExtrema() call need only be made once per
                                subtable.
            
            editor              The Editor object containing this subtable.
        
        >>> obj, ed = _makeTest()
        >>> d = obj.effectExtrema(forHorizontal=False, editor=ed)
        >>> for g in sorted(d):
        ...   print(g, d[g])
        80 (0, -25)
        81 (0, -29)
        82 (0, -25)
        """

        cache = kwArgs.pop('cache', {})

        if id(self) in cache:
            return cache[id(self)]

        r = {}
        revMap = utilities.invertDictFull(self.classDefInput, asSets=True)
        c0Glyphs = None

        for key, lkGroup in self.items():
            rCumul = {}

            for lkRec in lkGroup:
                cls = key[1][lkRec.sequenceIndex]
                lk = lkRec.lookup

                if cls:
                    gSet = revMap[cls]

                else:
                    if c0Glyphs is None:
                        count = utilities.getFontGlyphCount(**kwArgs)
                        c0Glyphs = set(range(count))

                        for remove in revMap.values():
                            c0Glyphs -= remove

                    gSet = c0Glyphs

                for subtable in lk:
                    rSub = subtable.effectExtrema(forHorizontal,
                                                  cache=cache,
                                                  **kwArgs)

                    for glyph in gSet:
                        if glyph in rSub:
                            if glyph not in rCumul:
                                rCumul[glyph] = rSub[glyph]

                            else:
                                if rCumul[glyph][0] is None or rSub[glyph][
                                        0] is None:
                                    new0 = None
                                else:
                                    new0 = rCumul[glyph][0] + rSub[glyph][0]

                                if rCumul[glyph][1] is None or rSub[glyph][
                                        1] is None:
                                    new1 = None
                                else:
                                    new1 = rCumul[glyph][1] + rSub[glyph][1]

                                rCumul[glyph] = (new0, new1)

            for glyph, (valHi, valLo) in rCumul.items():
                if glyph not in r:
                    r[glyph] = (valHi, valLo)

                else:
                    oldHi, oldLo = r[glyph]

                    if oldHi is None or valHi is None:
                        newHi = None
                    else:
                        newHi = max(oldHi, valHi)

                    if oldLo is None or valLo is None:
                        newLo = None
                    else:
                        newLo = min(oldLo, valLo)

                    r[glyph] = (newHi, newLo)

        cache[id(self)] = r
        return r
예제 #4
0
    def fromvalidatedwalker(cls, w, **kwArgs):
        """
        Like fromwalker(), this method returns a new AttachListTable. However,
        it also does extensive validation via the logging module (the client
        should have done a logging.basicConfig call prior to calling this
        method, unless a logger is passed in via the 'logger' keyword
        argument).
        
        >>> logger = utilities.makeDoctestLogger('test.GDEF')
        >>> fvb = AttachListTable.fromvalidatedbytes
        >>> s = _testingValues[1].binaryString()
        >>> obj = fvb(s, logger=logger)
        test.GDEF.attachlist - DEBUG - Walker has 34 remaining bytes.
        test.GDEF.attachlist.coverage - DEBUG - Walker has 24 remaining bytes.
        test.GDEF.attachlist.coverage - DEBUG - Format is 1, count is 3
        test.GDEF.attachlist.coverage - DEBUG - Raw data are [45, 50, 98]
        test.GDEF.attachlist - INFO - Number of groups: 3.
        
        >>> fvb(s[:1], logger=logger)
        test.GDEF.attachlist - DEBUG - Walker has 1 remaining bytes.
        test.GDEF.attachlist - ERROR - Insufficient bytes.
        
        >>> fvb(s[:2], logger=logger)
        test.GDEF.attachlist - DEBUG - Walker has 2 remaining bytes.
        test.GDEF.attachlist.coverage - DEBUG - Walker has 0 remaining bytes.
        test.GDEF.attachlist.coverage - ERROR - Insufficient bytes.
        """

        logger = kwArgs.pop('logger', None)

        if logger is None:
            logger = logging.getLogger().getChild('attachlist')
        else:
            logger = logger.getChild('attachlist')

        logger.debug(
            ('V0001', (w.length(), ), "Walker has %d remaining bytes."))

        if w.length() < 2:
            logger.error(('V0004', (), "Insufficient bytes."))
            return None

        covOffset = w.unpack("H")

        covTable = coverage.Coverage.fromvalidatedwalker(
            w.subWalker(covOffset), logger=logger, **kwArgs)

        if covTable is None:
            return None

        revTable = utilities.invertDictFull(covTable)
        localPool = {}
        fw = attachpoint.AttachPointTable.fromwalker

        if w.length() < 2:
            logger.error(('V0096', (), "Insufficient bytes for group count."))
            return None

        groupCount = w.unpack("H")
        logger.info(('V0097', (groupCount, ), "Number of groups: %d."))

        if w.length() < 2 * groupCount:
            logger.error(
                ('V0098', (), "Insufficient bytes for group offsets."))

            return None

        offsets = w.group("H", groupCount)
        r = cls()
        okToProceed = True

        for coverIndex, offset in enumerate(offsets):
            if offset:
                if offset not in localPool:
                    subLogger = logger.getChild("[%d]" % (coverIndex, ))
                    obj = fw(w.subWalker(offset), logger=subLogger, **kwArgs)

                    if obj is None:
                        okToProceed = False

                    localPool[offset] = obj

                thisList = localPool[offset]

                if thisList is not None:
                    for glyphIndex in revTable.get(coverIndex, []):
                        r[glyphIndex] = thisList

        return (r if okToProceed else None)
예제 #5
0
    def fromformat0(cls, f0):
        """
        Creates and returns a new Format2 object derived from the specified
        Format0 object. This method can take a while to execute, as it needs to
        go through and build up deductions about the list of class members for
        both the left-hand and right-hand sides.
        """

        assert f0.format == 0
        allLefts = set(t[0] for t in f0)
        allRights = set(t[1] for t in f0)
        dLefts = collections.defaultdict(set)
        dRights = collections.defaultdict(set)

        for lg in allLefts:
            v = []

            for (lgTest, rg), value in f0.items():
                if lgTest == lg:
                    v.append((value, rg))

            t = tuple(sorted(v))
            dLefts[t].add(lg)

        for rg in allRights:
            v = []

            for (lg, rgTest), value in f0.items():
                if rgTest == rg:
                    v.append((value, lg))

            t = tuple(sorted(v))
            dRights[t].add(rg)

        leftCD = classdef.ClassDef()
        rightCD = classdef.ClassDef()

        for i, v in enumerate(sorted(sorted(s) for s in dLefts.values()), 1):
            for glyph in v:
                leftCD[glyph] = i

        for i, v in enumerate(sorted(sorted(s) for s in dRights.values()), 1):
            for glyph in v:
                rightCD[glyph] = i

        invLeft = utilities.invertDictFull(leftCD)
        invRight = utilities.invertDictFull(rightCD)

        if not f0.coverage.format:
            newCov = coverage_v1.Coverage(vertical=f0.coverage.vertical,
                                          crossStream=f0.coverage.crossStream)

        else:
            newCov = f0.coverage

        r = cls({},
                leftClassDef=leftCD,
                rightClassDef=rightCD,
                coverage=newCov,
                tupleIndex=f0.tupleIndex)

        CP = classpair.ClassPair

        for leftClass, vLeft in invLeft.items():
            for rightClass, vRight in invRight.items():
                key = (vLeft[0], vRight[0])

                if key in f0:
                    r[CP([leftClass, rightClass])] = f0[key]

        return r
예제 #6
0
    def buildBinary(self, w, **kwArgs):
        """
        Adds the binary data for the ContextClass to the specified
        LinkedWriter.
        
        NOTE! There will be unresolved lookup list indices in the LinkedWriter
        after this method is finished. The caller (or somewhere higher up) is
        responsible for adding an index map to the LinkedWriter with the tag
        "lookupList" before the LinkedWriter's binaryString() method is called.
        
        >>> w = writer.LinkedWriter()
        >>> obj = _testingValues[1]
        >>> obj.buildBinary(w, forGPOS=False)
        >>> d = {obj[k][0].lookup.asImmutable(): 22 for k in obj}
        >>> w.addIndexMap("lookupList_GSUB", d)
        >>> utilities.hexdump(w.binaryString())
               0 | 0002 0014 001C 0026  003C 0004 0000 0044 |.......&.<.....D|
              10 | 0000 0000 0001 0002  0014 0015 0001 000A |................|
              20 | 0002 0001 0001 0002  0003 0014 0015 0001 |................|
              30 | 0016 0016 0002 0028  0029 0003 0001 001E |.......(.)......|
              40 | 0001 0001 0001 0004  0001 0001 0002 0002 |................|
              50 | 0001 0001 0001 0000  0016                |..........      |
        """

        if 'stakeValue' in kwArgs:
            stakeValue = kwArgs.pop('stakeValue')
            w.stakeCurrentWithValue(stakeValue)
        else:
            stakeValue = w.stakeCurrent()

        w.add("H", 2)  # format

        inputClassToGlyphs = utilities.invertDictFull(self.classDefInput,
                                                      asSets=True)

        firstInputClasses = set(k[1][0] for k in self)
        firstInputClassesSorted = sorted(firstInputClasses)

        firstInputGlyphs = functools.reduce(set.union,
                                            (inputClassToGlyphs[c]
                                             for c in firstInputClasses))

        covTable = coverage.Coverage.fromglyphset(firstInputGlyphs)
        covStake = w.getNewStake()
        w.addUnresolvedOffset("H", stakeValue, covStake)

        v = [
            self.classDefBacktrack, self.classDefInput, self.classDefLookahead
        ]

        vImm = [(obj.asImmutable()[1] if obj else None) for obj in v]
        cdPool = {}
        objStakes = {}

        for cd, cdImm in zip(v, vImm):
            if len(cd):
                obj = cdPool.setdefault(cdImm, cd)

                objStake = objStakes.setdefault(obj.asImmutable(),
                                                w.getNewStake())

                w.addUnresolvedOffset("H", stakeValue, objStake)

            else:
                w.add("H", 0)

        count = utilities.safeMax(self.classDefInput.values(), -1) + 1
        w.add("H", count)

        setStakes = dict((firstInputClass, w.getNewStake())
                         for firstInputClass in firstInputClassesSorted)

        for firstInputClass in range(count):
            if firstInputClass in firstInputClasses:
                w.addUnresolvedOffset("H", stakeValue,
                                      setStakes[firstInputClass])

            else:
                w.add("H", 0)

        covTable.buildBinary(w, stakeValue=covStake)

        for cdImm in sorted(cdPool):  # sort to guarantee repeatable ordering
            obj = cdPool[cdImm]
            obj.buildBinary(w, stakeValue=objStakes[obj.asImmutable()])

        orderings = {}
        ruleStakes = {}

        for firstInputClass in firstInputClassesSorted:
            setStake = setStakes[firstInputClass]
            w.stakeCurrentWithValue(setStake)

            o = orderings[firstInputClass] = sorted(
                (k.ruleOrder, k[1], k) for k in self
                if k[1][0] == firstInputClass)

            w.add("H", len(o))

            for order, ignore, key in o:
                ruleStake = w.getNewStake()
                ruleStakes[(firstInputClass, order)] = ruleStake
                w.addUnresolvedOffset("H", setStake, ruleStake)

        for firstInputClass in firstInputClassesSorted:
            for order, ignore, key in orderings[firstInputClass]:
                w.stakeCurrentWithValue(ruleStakes[(firstInputClass, order)])
                obj = self[key]
                w.add("H", len(key[0]))
                w.addGroup("H", reversed(key[0]))
                w.add("H", len(key[1]))
                w.addGroup("H", key[1][1:])
                w.add("H", len(key[2]))
                w.addGroup("H", key[2])
                w.add("H", len(obj))
                obj.buildBinary(w, **kwArgs)
예제 #7
0
    def fromformat0(cls, f0):
        """
        Creates and returns a new Format2 object derived from the specified
        Format0 object. This method can take a while to execute, as it needs to
        go through and build up deductions about the list of class members for
        both the left-hand and right-hand sides.
        
        >>> Format2.fromformat0(_f0tv()[1]).pprint()
        ClassPair((1, 1)): -25
        ClassPair((1, 3)): -30
        ClassPair((2, 2)): 12
        Left-hand classes:
          14: 1
          18: 2
        Right-hand classes:
          23: 1
          38: 2
          96: 3
        Header information:
          Horizontal
          With-stream
          No variation kerning
          Process forward
        """

        assert f0.format == 0
        allLefts = set(t[0] for t in f0)
        allRights = set(t[1] for t in f0)
        dLefts = collections.defaultdict(set)
        dRights = collections.defaultdict(set)

        for lg in allLefts:
            v = []

            for (lgTest, rg), value in f0.items():
                if lgTest == lg:
                    v.append((value, rg))

            t = tuple(sorted(v))
            dLefts[t].add(lg)

        for rg in allRights:
            v = []

            for (lg, rgTest), value in f0.items():
                if rgTest == rg:
                    v.append((value, lg))

            t = tuple(sorted(v))
            dRights[t].add(rg)

        leftCD = classdef.ClassDef()
        rightCD = classdef.ClassDef()

        for i, v in enumerate(sorted(sorted(s) for s in dLefts.values()), 1):
            for glyph in v:
                leftCD[glyph] = i

        for i, v in enumerate(sorted(sorted(s) for s in dRights.values()), 1):
            for glyph in v:
                rightCD[glyph] = i

        invLeft = utilities.invertDictFull(leftCD)
        invRight = utilities.invertDictFull(rightCD)

        r = cls({},
                leftClassDef=leftCD,
                rightClassDef=rightCD,
                coverage=f0.coverage,
                tupleIndex=f0.tupleIndex)

        CP = classpair.ClassPair

        for leftClass, vLeft in invLeft.items():
            for rightClass, vRight in invRight.items():
                key = (vLeft[0], vRight[0])

                if key in f0:
                    r[CP([leftClass, rightClass])] = f0[key]

        return r
예제 #8
0
 def effectExtrema(self, forHorizontal=True, **kwArgs):
     """
     Returns a dict, indexed by glyph, of pairs of values. If
     forHorizontal is True, these values will be the yMaxDelta and
     yMinDelta; if False, they will be the xMaxDelta and xMinDelta. These
     values can then be used to test against the font's ascent/descent
     values in order to show VDMA-like output, or to be accumulated across
     all the features that are performed for a given script and lang/sys.
     
     Note that either or both of these values may be None; this can arise
     for cases like mark-to-mark, where potentially infinite stacking of
     marks can occur.
     
     The following keyword arguments are used:
         
         cache               A dict mapping object IDs to result dicts.
                             This is used during processing to speed up
                             analysis of deeply nested subtables, so the
                             effectExtrema() call need only be made once per
                             subtable.
         
         editor              The Editor object containing this subtable.
     
     >>> K = pairclasses_key.Key
     >>> V = value.Value
     >>> PV = pairvalues.PairValues
     >>> obj = PairClasses({
     ...   K([1, 2]): PV(V(xAdvance=20), V(xPlacement=30)),
     ...   K([2, 1]): PV(V(xAdvance=20), V(yPlacement=30)),
     ...   K([2, 2]): PV(V(xAdvance=20), V(xAdvance=30))},
     ...   classDef1 = classdef.ClassDef({40: 1, 50: 2, 60: 1}),
     ...   classDef2 = classdef.ClassDef({75: 1, 92: 2}))
     >>> d = obj.effectExtrema(forHorizontal=True)
     >>> for g in sorted(d):
     ...   print(g, d[g])
     75 (30, 0)
     
     >>> d = obj.effectExtrema(forHorizontal=False)
     >>> for g in sorted(d):
     ...   print(g, d[g])
     92 (30, 0)
     """
     
     cache = kwArgs.get('cache', {})
     
     if id(self) in cache:
         return cache[id(self)]
     
     r = {}
     fv = effect.Effect.fromvalue
     others1 = others2 = None  # only fill these on-demand
     invMap1 = utilities.invertDictFull(self.classDef1, asSets=True)
     invMap2 = utilities.invertDictFull(self.classDef2, asSets=True)
     
     for (cls1, cls2), pvObj in self.items():
         if pvObj.first is not None:
             p = fv(pvObj.first).toPair(forHorizontal)
             
             if any(p):
                 if cls1:
                     glyphSet = invMap1[cls1]
                 
                 else:
                     if others1 is None:
                         fgc = utilities.getFontGlyphCount(**kwArgs)
                         others1 = set(range(fgc)) - set(self.classDef1)
                     
                     glyphSet = others1
                 
                 for g in glyphSet:
                     if g not in r:
                         r[g] = p
                     
                     else:
                         old = r[g]
                         
                         r[g] = tuple((
                           max(old[0], p[0]),
                           min(old[1], p[1])))
         
         if pvObj.second is not None:
             p = fv(pvObj.second).toPair(forHorizontal)
             
             if any(p):
                 if cls2:
                     glyphSet = invMap2[cls2]
                 
                 else:
                     if others2 is None:
                         fgc = utilities.getFontGlyphCount(**kwArgs)
                         others2 = set(range(fgc)) - set(self.classDef2)
                     
                     glyphSet = others2
                 
                 for g in glyphSet:
                     if g not in r:
                         r[g] = p
                     
                     else:
                         old = r[g]
                         
                         r[g] = tuple((
                           max(old[0], p[0]),
                           min(old[1], p[1])))
     
     cache[id(self)] = r
     return r