Ejemplo n.º 1
0
 def _makeTV():
     from fontio3.GSUB import single
     
     from fontio3.opentype import (
       classdef,
       lookup,
       pschainclass_classtuple,
       pschainclass_key,
       pslookupgroup,
       pslookuprecord)
     
     single_obj = single.Single({2: 41, 4: 42, 6: 43})
     lookup_obj = lookup.Lookup([single_obj], sequence=0)
     pslookuprecord_obj = pslookuprecord.PSLookupRecord(0, lookup_obj)
     pslookupgroup_obj = pslookupgroup.PSLookupGroup([pslookuprecord_obj])
     tBack = pschainclass_classtuple.ClassTuple([1, 2])
     tIn = pschainclass_classtuple.ClassTuple([1])
     tLook = pschainclass_classtuple.ClassTuple([1])
     key_obj = pschainclass_key.Key([tBack, tIn, tLook])
     cdBack = classdef.ClassDef({51: 1, 52: 1, 54: 2, 55: 2})
     cdIn = classdef.ClassDef({2: 1, 4: 1, 6: 1})
     cdLook = classdef.ClassDef({80: 1, 81: 1, 82: 1})
     
     return ChainClass(
       {key_obj: pslookupgroup_obj},
       classDefBacktrack = cdBack,
       classDefInput = cdIn,
       classDefLookahead = cdLook)
Ejemplo n.º 2
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
Ejemplo n.º 3
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
Ejemplo n.º 4
0
    def _makeTest():
        """
        The test case here recognizes the glyph sequence 1-2-3, and adjusts the
        positioning of class 2.
        """

        from fontio3 import hmtx, utilities
        from fontio3.GPOS import single, value

        from fontio3.opentype import (classdef, lookup,
                                      pschainclass_classtuple,
                                      pschainclass_key, pslookupgroup,
                                      pslookuprecord)

        v1 = value.Value(xPlacement=-25)
        v2 = value.Value(xPlacement=-29)
        s1 = single.Single({80: v1, 81: v2, 82: v1})
        lk1 = lookup.Lookup([s1])
        psr1 = pslookuprecord.PSLookupRecord(0, lk1)
        psg = pslookupgroup.PSLookupGroup([psr1])
        backPart = pschainclass_classtuple.ClassTuple([1])
        inPart = pschainclass_classtuple.ClassTuple([1])
        lookPart = pschainclass_classtuple.ClassTuple([1])
        key = pschainclass_key.Key([backPart, inPart, lookPart])
        cdBack = classdef.ClassDef({25: 1})
        cdIn = classdef.ClassDef({80: 1, 81: 1, 82: 1})
        cdLook = classdef.ClassDef({26: 1})

        r = ChainClass({key: psg},
                       classDefBacktrack=cdBack,
                       classDefInput=cdIn,
                       classDefLookahead=cdLook)

        e = utilities.fakeEditor(0x10000)
        e.hmtx = hmtx.Hmtx()
        e.hmtx[25] = hmtx.MtxEntry(900, 50)
        e.hmtx[26] = hmtx.MtxEntry(970, 40)
        e.hmtx[80] = hmtx.MtxEntry(1020, 55)
        e.hmtx[81] = hmtx.MtxEntry(1090, 85)
        e.hmtx[82] = hmtx.MtxEntry(1050, 70)

        return r, e
Ejemplo n.º 5
0
    def _makeTV():
        from fontio3.GSUB import single

        cdBack = classdef.ClassDef({10: 1, 11: 1})
        cdIn = classdef.ClassDef({20: 1, 21: 1, 22: 2, 40: 3, 41: 3})
        cdLook = classdef.ClassDef({30: 1})
        back1 = pschainclass_classtuple.ClassTuple([1])
        in1 = pschainclass_classtuple.ClassTuple([1, 2])
        look1 = pschainclass_classtuple.ClassTuple([1])
        key1 = pschainclass_key.Key([back1, in1, look1], ruleOrder=0)
        sgl1 = single.Single({20: 40, 21: 41})
        lkup1 = lookup.Lookup([sgl1], sequence=22)
        rec1 = pslookuprecord.PSLookupRecord(sequenceIndex=0, lookup=lkup1)
        grp1 = pslookupgroup.PSLookupGroup([rec1])

        obj = PSChainClass({key1: grp1},
                           classDefBacktrack=cdBack,
                           classDefInput=cdIn,
                           classDefLookahead=cdLook)

        return obj
Ejemplo n.º 6
0
    def _makeTV():
        from fontio3.GSUB import single

        from fontio3.opentype import (classdef, lookup, pscontextclass_key,
                                      pslookupgroup, pslookuprecord)

        single_obj = single.Single({2: 41, 8: 42})
        lookup_obj = lookup.Lookup([single_obj], sequence=0)
        pslookuprecord_obj = pslookuprecord.PSLookupRecord(1, lookup_obj)
        pslookupgroup_obj = pslookupgroup.PSLookupGroup([pslookuprecord_obj])
        key_obj = pscontextclass_key.Key([1, 2, 1, 3])
        classdef_obj = classdef.ClassDef({19: 1, 2: 2, 8: 2, 12: 3})

        return ContextClass({key_obj: pslookupgroup_obj},
                            classDef=classdef_obj)
Ejemplo n.º 7
0
 def _makeTest():
     """
     The test case here recognizes the glyph sequence 1-2-3, and adjusts the
     positioning of classes 1 and 3.
     """
     
     from fontio3 import hmtx, utilities
     from fontio3.GPOS import single, value
     
     from fontio3.opentype import (
       classdef,
       lookup,
       pscontextclass_key,
       pslookupgroup,
       pslookuprecord)
     
     v1 = value.Value(xPlacement=-25)
     v2 = value.Value(xPlacement=-29)
     s1 = single.Single({20: v1, 25: v2})
     lk1 = lookup.Lookup([s1])
     psr1 = pslookuprecord.PSLookupRecord(0, lk1)
     s2 = single.Single({26: value.Value(xPlacement=25)})
     lk2 = lookup.Lookup([s2])
     psr2 = pslookuprecord.PSLookupRecord(2, lk2)
     psg = pslookupgroup.PSLookupGroup([psr1, psr2])
     key = pscontextclass_key.Key([1, 2, 3])
     cd = classdef.ClassDef({20: 1, 25: 1, 80: 2, 81: 2, 26: 3})
     r = ContextClass({key: psg}, classDef=cd)
     e = utilities.fakeEditor(0x10000)
     e.hmtx = hmtx.Hmtx()
     e.hmtx[20] = hmtx.MtxEntry(910, 42)
     e.hmtx[25] = hmtx.MtxEntry(900, 50)
     e.hmtx[26] = hmtx.MtxEntry(970, 40)
     e.hmtx[80] = hmtx.MtxEntry(1020, 55)
     e.hmtx[81] = hmtx.MtxEntry(1090, 85)
     
     return r, e
Ejemplo n.º 8
0
    def fromValidatedFontWorkerSource(cls, fws, **kwArgs):
        """
        Creates and returns a new PairClasses object from the specified
        FontWorkerSource, with validation of the source data.
        
        >>> logger = utilities.makeDoctestLogger("FW_test")
        >>> pc = PairClasses.fromValidatedFontWorkerSource(_test_FW_fws2, namer=_test_FW_namer, logger=logger)
        FW_test.pairclasses - ERROR - line 13 -- unexpected token: foo
        >>> pc.pprint()
        (First class 1, Second class 2):
          First adjustment:
            FUnit adjustment to origin's x-coordinate: -123
        (First class 2, Second class 1):
          Second adjustment:
            FUnit adjustment to origin's x-coordinate: -456
        Class definition table for first glyph:
          2: 1
          3: 2
        Class definition table for second glyph:
          23: 1
          29: 2
        """

        terminalStrings = ('subtable end', 'lookup end')
        startingLineNumber=fws.lineNumber
        iskernset = kwArgs.get('iskernset', False)
        logger = kwArgs.pop('logger', logging.getLogger())
        logger = logger.getChild("pairclasses")
        fvfws = classdef.ClassDef.fromValidatedFontWorkerSource
        PV = pairvalues.PairValues
        V = value.Value
        
        tokenSet = frozenset({
          'left x advance',
          'left x placement',
          'left y advance',
          'left y placement',
          'right x advance',
          'right x placement',
          'right y advance',
          'right y placement'})

        # place-holders
        classDef1 = classdef.ClassDef()
        classDef2 = classdef.ClassDef()
        pairDict = defaultdict(lambda: [None, None])
        
        for line in fws:
            if line.lower().strip() in terminalStrings:
                if iskernset and line.lower() == 'subtable end':
                    continue
                
                else:
                    npd, nc1, nc2 = _reindexed(
                      pairDict,
                      classDef1,
                      classDef2,
                      logger = logger)
                    
                    return cls(npd, classDef1=nc1, classDef2=nc2)

            if len(line) > 0:
                tokens = [x.strip() for x in line.split('\t')]

                if tokens[0].lower() == 'firstclass definition begin':
                    classDef1 = fvfws(fws, logger=logger, **kwArgs)

                elif tokens[0].lower() == 'secondclass definition begin':
                    classDef2 = fvfws(fws, logger=logger, **kwArgs)

                elif tokens[0].lower() in tokenSet:
                    class1 = int(tokens[1])
                    class2 = int(tokens[2])
                    key = (class1, class2)
                    val= int(tokens[3])
                    
                    if val != 0:
                        pval = pairDict[key]
                        if tokens[0].lower() == 'left x advance':
                            if pval[0] and pval[0].xAdvance:
                                logger.warning((
                                  'Vxxxx',
                                  (fws.lineNumber, tokens[0], tokens[1], tokens[2]),
                                  "line %d -- ignoring duplicate %s for class "
                                  "pair %s,%s"))
                            else:
                                if pval[0] is None:
                                    pval[0] = value.Value()
                                pval[0].xAdvance=val

                        elif tokens[0].lower() == 'right x advance':
                            if pval[1] and pval[1].xAdvance:
                                logger.warning((
                                  'Vxxxx',
                                  (fws.lineNumber, tokens[0], tokens[1], tokens[2]),
                                  "line %d -- ignoring duplicate %s for class "
                                  "pair %s,%s"))
                            else:
                                if pval[1] is None:
                                    pval[1] = value.Value()
                                pval[1].xAdvance=val

                        elif tokens[0].lower() == 'left x placement':
                            if pval[0] and pval[0].xPlacement:
                                logger.warning((
                                  'Vxxxx',
                                  (fws.lineNumber, tokens[0], tokens[1], tokens[2]),
                                  "line %d -- ignoring duplicate %s for class "
                                  "pair %s,%s"))
                            else:
                                if pval[0] is None:
                                    pval[0] = value.Value()
                                pval[0].xPlacement = val

                        elif tokens[0].lower() == 'right x placement':
                            if pval[1] and pval[1].xPlacement:
                                logger.warning((
                                  'Vxxxx',
                                  (fws.lineNumber, tokens[0], tokens[1], tokens[2]),
                                  "line %d -- ignoring duplicate %s for class "
                                  "pair %s,%s"))
                            else:
                                if pval[1] is None:
                                    pval[1] = value.Value()
                                pval[1].xPlacement = val

                        elif tokens[0].lower() == 'left y advance':
                            if pval[0] and pval[0].yAdvance:
                                logger.warning((
                                  'Vxxxx',
                                  (fws.lineNumber, tokens[0], tokens[1], tokens[2]),
                                  "line %d -- ignoring duplicate %s for class "
                                  "pair %s,%s"))
                            else:
                                if pval[0] is None:
                                    pval[0] = value.Value()
                                pval[0].yAdvance=val

                        elif tokens[0].lower() == 'right y advance':
                            if pval[1] and pval[1].yAdvance:
                                logger.warning((
                                  'Vxxxx',
                                  (fws.lineNumber, tokens[0], tokens[1], tokens[2]),
                                  "line %d -- ignoring duplicate %s for class "
                                  "pair %s,%s"))
                            else:
                                if pval[1] is None:
                                    pval[1] = value.Value()
                                pval[1].yAdvance=val

                        elif tokens[0].lower() == 'left y placement':
                            if pval[0] and pval[0].yPlacement:
                                logger.warning((
                                  'Vxxxx',
                                  (fws.lineNumber, tokens[0], tokens[1], tokens[2]),
                                  "line %d -- ignoring duplicate %s for class "
                                  "pair %s,%s"))
                            else:
                                if pval[0] is None:
                                    pval[0] = value.Value()
                                pval[0].yPlacement=val

                        elif tokens[0].lower() == 'right y placement':
                            if pval[1] and pval[1].yPlacement:
                                logger.warning((
                                  'Vxxxx',
                                  (fws.lineNumber, tokens[0], tokens[1], tokens[2]),
                                  "line %d -- ignoring duplicate %s for class "
                                  "pair %s,%s"))
                            else:
                                if pval[1] is None:
                                    pval[1] = value.Value()
                                pval[1].yPlacement=val

                else:
                    logger.error((
                        'V0960',
                        (fws.lineNumber, tokens[0]),
                        'line %d -- unexpected token: %s'))
                
        logger.error((
            'V0958',
            (startingLineNumber, "/".join(terminalStrings)),
            'line %d -- did not find matching \'%s\''))

        if pairDict and classDef1 and classDef2:
            r = cls(pairDict, classDef1=classDef1, classDef2=classDef2)

        else:
            logger.error((
              'Vxxxx',
              (),
              "Incomplete or invalid lookup data for pair classes."))
            
            r = None

        return r
Ejemplo n.º 9
0
        pass


if __debug__:
    from fontio3.kerx import coverage

    _testingValues = (Format3(),
                      Format3(
                          {
                              ClassPair([1, 1]): -25,
                              ClassPair([1, 2]): -10,
                              ClassPair([2, 1]): 15
                          },
                          leftClassDef=classdef.ClassDef({
                              15: 1,
                              25: 1,
                              35: 2
                          }),
                          rightClassDef=classdef.ClassDef({
                              9: 1,
                              12: 1,
                              15: 1,
                              40: 2
                          }),
                          coverage=coverage.Coverage()))


def _test():
    import doctest
    doctest.testmod()
Ejemplo n.º 10
0
def _reindexed(pairmap, cd1, cd2, **kwArgs):
    """
    Return a 3-tuple consisting of a re-mapped pairmap, classDef1, and
    classDef2, removing all non-contiguous class entries and unused keys.
    """
    logger = kwArgs['logger']

    # first clean up
    cd1v = set(cd1.values())
    cd2v = set(cd2.values())

    newpairs_1 = {}
    for pairkey in pairmap:
        if pairkey[0] in cd1v and pairkey[1] in cd2v:
            newpairs_1[pairkey] = pairmap[pairkey]
        else:
            logger.warning((
                'Vxxxx',
                (pairkey,),
                "Removing class pair %s: first or second not listed in class defs"))

    k0 = [k[0] for k in newpairs_1]
    k1 = [k[1] for k in newpairs_1]

    cd1todelete = [k for k,v in cd1.items() if v not in k0]
    cd2todelete = [k for k,v in cd2.items() if v not in k1]

    for k in cd1todelete:
        logger.warning((
            'Vxxxx',
            (k, cd1[k]),
            "Removing glyph %d from firstclasses because its "
            "class (%d) is not used in any PairClasses key"))
        del(cd1[k])
        
    for k in cd2todelete:
        logger.warning((
            'Vxxxx',
            (k, cd2[k]),
            "Removing glyph %d from secondclasses because its "
            "class (%d) is not used in any PairClasses key"))
        del(cd2[k])


    # now reindex
    oldtonew1 = _getremapping(cd1)
    oldtonew2 = _getremapping(cd2)

    newpairs_2 = {}
    for pairkey in newpairs_1:
        newkey = (oldtonew1.get(pairkey[0]), oldtonew2.get(pairkey[1]))
        if newkey[0] and newkey[1]:
            vv = pairmap[pairkey]
            newpairs_2[newkey] = pairvalues.PairValues(vv[0], vv[1])
        else:
            logger.warning((
                'Vxxxx',
                (pairkey,),
                "Removing class pair %s: first or second class empty or missing"))
                
    newcd1 = {g:oldtonew1[cl] for g,cl in cd1.items()}
    newcd2 = {g:oldtonew2[cl] for g,cl in cd2.items()}

    return newpairs_2, classdef.ClassDef(newcd1), classdef.ClassDef(newcd2)
Ejemplo n.º 11
0
    def fromwalker(cls, w, **kwArgs):
        """
        Creates and returns a Format2 object from the specified walker. The
        following keyword arguments are used:
        
            coverage    A Coverage object (for either version 0 or version 1
                        'kern' tables).
            
            tupleIndex  The tuple index (or 0) for a version 1 'kern' table, or
                        None for a version 0 'kern' table.
        
        >>> obj = _testingValues[1]
        >>> obj == Format2.frombytes(
        ...   obj.binaryString(),
        ...   coverage = coverage_v1.Coverage())
        True
        """

        headerAdj = (6 if kwArgs['coverage'].format == 0 else 8)
        rowBytes = w.unpack("H")
        nCols = rowBytes // 2
        leftOffset, rightOffset, arrayBase = w.unpack("3H")

        # Determine nRows, by finding the largest offset present in the left
        # class table.

        wLeft = w.subWalker(leftOffset - headerAdj)
        leftFirstGlyph, leftCount = wLeft.unpack("2H")
        vLeft = wLeft.group("H", leftCount)
        nRows = 1 + ((max(vLeft) - arrayBase) // rowBytes)
        wArray = w.subWalker(arrayBase - headerAdj)
        arrayData = wArray.group("h" * nCols, nRows)

        # Make the two ClassDef objects

        leftCD = classdef.ClassDef()

        for n in vLeft:
            cIndex = (n - arrayBase) // rowBytes

            if cIndex:
                leftCD[leftFirstGlyph] = cIndex

            leftFirstGlyph += 1

        wRight = w.subWalker(rightOffset - headerAdj)
        d = classdef.ClassDef.fromwalker(wRight, forceFormat=1)
        rightCD = classdef.ClassDef((k, v // 2) for k, v in d.items() if v)

        # At this point, arrayData is a list of nRows rows, each containing
        # nCols kerning values. Since we know nRows and nCols explicitly, all
        # we need to do at this point is walk the data setting the dict's
        # values directly.

        for delKey in {'leftClassDef', 'rightClassDef'}:
            kwArgs.pop(delKey, None)

        r = cls({},
                leftClassDef=leftCD,
                rightClassDef=rightCD,
                **utilities.filterKWArgs(cls, kwArgs))

        CP = classpair.ClassPair

        for rowIndex, row in enumerate(arrayData):
            for colIndex, value in enumerate(row):
                if value:  # most will be zero, remember
                    r[CP([rowIndex, colIndex])] = value

        return r
Ejemplo n.º 12
0
    def fromvalidatedwalker(cls, w, **kwArgs):
        """
        Creates and returns a Format 2 object from the specified walker, doing
        source validation.
        
        >>> s = _testingValues[1].binaryString()
        >>> logger = utilities.makeDoctestLogger("format2_fvw")
        >>> fvb = Format2.fromvalidatedbytes
        >>> cv = _testingValues[1].coverage.__copy__()
        >>> obj = fvb(s, logger=logger, coverage=cv)
        format2_fvw.format2 - DEBUG - Walker has 140 remaining bytes.
        format2_fvw.format2.right classes.classDef - DEBUG - Walker has 68 remaining bytes.
        format2_fvw.format2.right classes.classDef - DEBUG - ClassDef is format 1.
        format2_fvw.format2.right classes.classDef - DEBUG - First is 9, and count is 32
        format2_fvw.format2.right classes.classDef - DEBUG - Raw data are (2, 0, 0, 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4)
        >>> obj == _testingValues[1]
        True
        
        >>> fvb(s[:3], logger=logger, coverage=cv)
        format2_fvw.format2 - DEBUG - Walker has 3 remaining bytes.
        format2_fvw.format2 - ERROR - Insufficient bytes.
        """

        kwArgs.pop('fontGlyphCount', None)
        logger = kwArgs.pop('logger', logging.getLogger())
        logger = logger.getChild("format2")

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

        headerAdj = (6 if kwArgs['coverage'].format == 0 else 8)

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

        rowBytes, leftOffset, rightOffset, arrayBase = w.unpack("4H")

        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

        # 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)

        if wLeft.length() < 4:
            logger.error(('V0608', (),
                          "The left-class table is missing or incomplete."))

            return None

        leftFirstGlyph, leftCount = wLeft.unpack("2H")

        if wLeft.length() < 2 * leftCount:
            logger.error(('V0608', (),
                          "The left-class table is missing or incomplete."))

            return None

        vLeft = wLeft.group("H", leftCount)
        nRows = 1 + ((max(vLeft) - arrayBase) // rowBytes)

        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."))

        arrayData = wArray.group("h" * nCols, nRows)

        # Make the two ClassDef objects

        leftCD = classdef.ClassDef()

        if any(n < arrayBase for n in vLeft):
            logger.error(
                ('V0611', (), "At least one left class index is negative."))

            return None

        for n in vLeft:
            cIndex, modCheck = divmod(n - arrayBase, rowBytes)

            if modCheck:
                logger.error(
                    ('V0612', (),
                     "At least one left offset is not an even multiple of "
                     "the rowBytes past the array base."))

                return None

            if cIndex:
                leftCD[leftFirstGlyph] = cIndex

            leftFirstGlyph += 1

        if rightOffset < headerAdj:
            logger.error(('V0614', (rightOffset, ),
                          "The rightOffset value %d is too small."))

            return None

        wRight = w.subWalker(rightOffset - headerAdj)

        d = classdef.ClassDef.fromvalidatedwalker(
            wRight, logger=logger.getChild("right classes"), forceFormat=1)

        if d is None:
            return None

        rightCD = classdef.ClassDef()

        for k, v in d.items():
            if v:
                cIndex, modCheck = divmod(v, 2)

                if modCheck:
                    logger.error((
                        'V0615', (),
                        "At least one right offset is not an even multiple of "
                        "the rowBytes past the array base."))

                    return None

                rightCD[k] = cIndex

        # At this point, arrayData is a list of nRows rows, each containing
        # nCols kerning values. Since we know nRows and nCols explicitly, all
        # we need to do at this point is walk the data setting the dict's
        # values directly.

        for delKey in {'leftClassDef', 'rightClassDef'}:
            kwArgs.pop(delKey, None)

        r = cls({},
                leftClassDef=leftCD,
                rightClassDef=rightCD,
                **utilities.filterKWArgs(cls, kwArgs))

        CP = classpair.ClassPair

        for rowIndex, row in enumerate(arrayData):
            for colIndex, value in enumerate(row):
                if value:  # most will be zero, remember
                    r[CP([rowIndex, colIndex])] = value

        return r
Ejemplo n.º 13
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
Ejemplo n.º 14
0
    def fromwalker(cls, w, **kwArgs):
        """
        Creates and returns a new PSChainClass from the specified walker.
        
        There is one required keyword argument:
        
            fixupList   A list, to which (lookupListIndex, fixupFunc) pairs
                        will be appended. The actual lookup won't be set in the
                        PSLookupRecord until this call is made, usually by the
                        top-level GPOS construction logic. The fixup call takes
                        one argument, the Lookup being set into it.
        
        >>> 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)
        >>> s = w.binaryString()
        >>> FL = []
        >>> obj2 = PSChainClass.frombytes(s, fixupList=FL)
        >>> d = {22: obj[k][0].lookup for k in obj}
        >>> for index, func in FL:
        ...     func(d[index])
        
        At this point we have the object; note that the reconciliation phase
        has removed some unneeded classes from the ClassDef (see the doctest
        output for the validated method to see more details on this).
        
        >>> obj2.pprint_changes(obj)
        Class definition table (input):
          Deleted records:
            40: 3
            41: 3
        """

        assert 'fixupList' in kwArgs
        format = w.unpack("H")
        assert format == 2
        covTable = coverage.Coverage.fromwalker(w.subWalker(w.unpack("H")))
        f = classdef.ClassDef.fromwalker

        backOffset = w.unpack("H")

        if backOffset:
            cdBack = f(w.subWalker(backOffset))
        else:
            cdBack = classdef.ClassDef()

        cdIn = classdef.ClassDef.fromwalker(w.subWalker(w.unpack("H")))

        lookOffset = w.unpack("H")

        if lookOffset:
            cdLook = f(w.subWalker(lookOffset))
        else:
            cdLook = classdef.ClassDef()

        r = cls({},
                classDefBacktrack=cdBack,
                classDefInput=cdIn,
                classDefLookahead=cdLook)

        setOffsets = w.group("H", w.unpack("H"))
        f = pslookupgroup.PSLookupGroup.fromwalker
        fixupList = kwArgs['fixupList']
        ClassTuple = pschainclass_classtuple.ClassTuple
        Key = pschainclass_key.Key

        for firstClassIndex, setOffset in enumerate(setOffsets):
            if setOffset:
                wSet = w.subWalker(setOffset)
                it = enumerate(wSet.group("H", wSet.unpack("H")))

                for ruleOrder, ruleOffset in it:
                    wRule = wSet.subWalker(ruleOffset)

                    tBack = ClassTuple(
                        reversed(wRule.group("H", wRule.unpack("H"))))

                    tIn = ClassTuple((firstClassIndex, ) +
                                     wRule.group("H",
                                                 wRule.unpack("H") - 1))

                    tLook = ClassTuple(wRule.group("H", wRule.unpack("H")))
                    key = Key([tBack, tIn, tLook], ruleOrder=ruleOrder)

                    r[key] = f(wRule,
                               count=wRule.unpack("H"),
                               fixupList=fixupList)

        # Now that we have the keys we can reconcile

        okToProceed, covSet = coverageutilities.reconcile(
            covTable, {k[1]
                       for k in r}, [cdIn], **kwArgs)

        r.coverageExtras.update(covSet - set(cdIn))

        if not okToProceed:
            r.clear()

        return r
Ejemplo n.º 15
0
    def fromvalidatedwalker(cls, w, **kwArgs):
        """
        Creates and returns a new PSChainClass object from the specified
        walker, doing source validation.
        
        >>> logger = utilities.makeDoctestLogger("pschainclass_test")
        >>> 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)
        >>> s = w.binaryString()
        >>> FL = []
        >>> fvb = PSChainClass.fromvalidatedbytes
        >>> obj2 = fvb(s, fixupList=FL, logger=logger)
        pschainclass_test.pschainclass - DEBUG - Walker has 90 remaining bytes.
        pschainclass_test.pschainclass - DEBUG - Format is 2
        pschainclass_test.pschainclass.coverage - DEBUG - Walker has 70 remaining bytes.
        pschainclass_test.pschainclass.coverage - DEBUG - Format is 1, count is 2
        pschainclass_test.pschainclass.coverage - DEBUG - Raw data are [20, 21]
        pschainclass_test.pschainclass - DEBUG - Backtrack offset is 28
        pschainclass_test.pschainclass.classDef - DEBUG - Walker has 62 remaining bytes.
        pschainclass_test.pschainclass.classDef - DEBUG - ClassDef is format 1.
        pschainclass_test.pschainclass.classDef - DEBUG - First is 10, and count is 2
        pschainclass_test.pschainclass.classDef - DEBUG - Raw data are (1, 1)
        pschainclass_test.pschainclass - DEBUG - Input offset is 38
        pschainclass_test.pschainclass.classDef - DEBUG - Walker has 52 remaining bytes.
        pschainclass_test.pschainclass.classDef - DEBUG - ClassDef is format 2.
        pschainclass_test.pschainclass.classDef - DEBUG - Count is 3
        pschainclass_test.pschainclass.classDef - DEBUG - Raw data are [(20, 21, 1), (22, 22, 2), (40, 41, 3)]
        pschainclass_test.pschainclass - DEBUG - Lookahead offset is 60
        pschainclass_test.pschainclass.classDef - DEBUG - Walker has 30 remaining bytes.
        pschainclass_test.pschainclass.classDef - DEBUG - ClassDef is format 1.
        pschainclass_test.pschainclass.classDef - DEBUG - First is 30, and count is 1
        pschainclass_test.pschainclass.classDef - DEBUG - Raw data are (1,)
        pschainclass_test.pschainclass - DEBUG - Set offsets are (0, 68, 0, 0)
        pschainclass_test.pschainclass.class index 0 - DEBUG - Set offset is zero
        pschainclass_test.pschainclass.class index 1 - DEBUG - Set offset is 68
        pschainclass_test.pschainclass.class index 1 - DEBUG - Rule count is 1
        pschainclass_test.pschainclass.class index 1 - DEBUG - Raw rule offsets are (4,)
        pschainclass_test.pschainclass.class index 1.rule order 0 - DEBUG - Backtrack count is 1
        pschainclass_test.pschainclass.class index 1.rule order 0 - DEBUG - Backtrack classes (reversed) are (1,)
        pschainclass_test.pschainclass.class index 1.rule order 0 - DEBUG - Input count is 2
        pschainclass_test.pschainclass.class index 1.rule order 0 - DEBUG - Input classes are (1, 2)
        pschainclass_test.pschainclass.class index 1.rule order 0 - DEBUG - Lookahead count is 1
        pschainclass_test.pschainclass.class index 1.rule order 0 - DEBUG - Lookahead classes are (1,)
        pschainclass_test.pschainclass.class index 1.rule order 0 - DEBUG - Action count is 1
        pschainclass_test.pschainclass.class index 1.rule order 0.pslookupgroup - DEBUG - Walker has 4 bytes remaining.
        pschainclass_test.pschainclass.class index 1.rule order 0.pslookupgroup.[0].pslookuprecord - DEBUG - Walker has 4 remaining bytes.
        pschainclass_test.pschainclass.class index 1.rule order 0.pslookupgroup.[0].pslookuprecord - DEBUG - Sequence index is 0
        pschainclass_test.pschainclass.class index 1.rule order 0.pslookupgroup.[0].pslookuprecord - DEBUG - Lookup index is 22
        pschainclass_test.pschainclass.class index 2 - DEBUG - Set offset is zero
        pschainclass_test.pschainclass.class index 3 - DEBUG - Set offset is zero
        pschainclass_test.pschainclass - WARNING - The classes [3] in the ClassDef are not used in any key, so the corresponding glyphs [40, 41] will be removed from it.
        pschainclass_test.pschainclass - INFO - The following glyphs appear only in the ClassDef and are not present in the Coverage: [22]
        pschainclass_test.pschainclass - INFO - The following glyphs appear in the Coverage and the ClassDef: [20, 21]
        >>> d = {22: obj[k][0].lookup for k in obj}
        >>> for index, func in FL:
        ...     func(d[index])
        
        >>> fvb(s[:33], logger=logger, fixupList=FL)
        pschainclass_test.pschainclass - DEBUG - Walker has 33 remaining bytes.
        pschainclass_test.pschainclass - DEBUG - Format is 2
        pschainclass_test.pschainclass.coverage - DEBUG - Walker has 13 remaining bytes.
        pschainclass_test.pschainclass.coverage - DEBUG - Format is 1, count is 2
        pschainclass_test.pschainclass.coverage - DEBUG - Raw data are [20, 21]
        pschainclass_test.pschainclass - DEBUG - Backtrack offset is 28
        pschainclass_test.pschainclass.classDef - DEBUG - Walker has 5 remaining bytes.
        pschainclass_test.pschainclass.classDef - DEBUG - ClassDef is format 1.
        pschainclass_test.pschainclass.classDef - ERROR - Insufficient bytes for format 1 header.
        """

        assert 'fixupList' in kwArgs
        fixupList = kwArgs.pop('fixupList')

        logger = kwArgs.pop('logger', logging.getLogger())
        logger = logger.getChild("pschainclass")

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

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

        format = w.unpack("H")

        if format != 2:
            logger.error(
                ('V0002', (format, ), "Expected format 2, but got format %d."))

            return None

        else:
            logger.debug(('Vxxxx', (), "Format is 2"))

        covTable = coverage.Coverage.fromvalidatedwalker(w.subWalker(
            w.unpack("H")),
                                                         logger=logger)

        if covTable is None:
            return None

        fvw = classdef.ClassDef.fromvalidatedwalker
        backOffset = w.unpack("H")
        logger.debug(('Vxxxx', (backOffset, ), "Backtrack offset is %d"))

        if backOffset:
            cdBack = fvw(w.subWalker(backOffset), logger=logger)

            if cdBack is None:
                return None

        else:
            cdBack = classdef.ClassDef()

        inOffset = w.unpack("H")
        logger.debug(('Vxxxx', (inOffset, ), "Input offset is %d"))
        cdIn = fvw(w.subWalker(inOffset), logger=logger)

        if cdIn is None:
            return None

        lookOffset = w.unpack("H")
        logger.debug(('Vxxxx', (lookOffset, ), "Lookahead offset is %d"))

        if lookOffset:
            cdLook = fvw(w.subWalker(lookOffset), logger=logger)

            if cdLook is None:
                return None

        else:
            cdLook = classdef.ClassDef()

        r = cls({},
                classDefBacktrack=cdBack,
                classDefInput=cdIn,
                classDefLookahead=cdLook)

        setCount = w.unpack("H")

        if w.length() < 2 * setCount:
            logger.error(
                ('V0378', (),
                 "The ChainClassSet offsets are missing or incomplete."))

            return None

        setOffsets = w.group("H", setCount)
        logger.debug(('Vxxxx', (setOffsets, ), "Set offsets are %s"))
        ClassTuple = pschainclass_classtuple.ClassTuple
        Key = pschainclass_key.Key
        fvw = pslookupgroup.PSLookupGroup.fromvalidatedwalker

        for firstClassIndex, setOffset in enumerate(setOffsets):
            subLogger = logger.getChild("class index %d" % (firstClassIndex, ))

            if setOffset:
                subLogger.debug(('Vxxxx', (setOffset, ), "Set offset is %d"))
                wSet = w.subWalker(setOffset)

                if wSet.length() < 2:
                    subLogger.error(
                        ('V0379', (),
                         "The ChainClassRuleCount is missing or incomplete."))

                    return None

                ruleCount = wSet.unpack("H")
                subLogger.debug(('Vxxxx', (ruleCount, ), "Rule count is %d"))

                if wSet.length() < 2 * ruleCount:
                    subLogger.error((
                        'V0380', (),
                        "The ChainClassRule offsets are missing or incomplete."
                    ))

                    return None

                ruleOffsets = wSet.group("H", ruleCount)

                subLogger.debug(
                    ('Vxxxx', (ruleOffsets, ), "Raw rule offsets are %s"))

                for ruleOrder, ruleOffset in enumerate(ruleOffsets):
                    wRule = wSet.subWalker(ruleOffset)

                    subLogger2 = subLogger.getChild("rule order %d" %
                                                    (ruleOrder, ))

                    if wRule.length() < 2:
                        subLogger2.error((
                            'V0381', (),
                            "The BacktrackGlyphCount is missing or incomplete."
                        ))

                        return None

                    backCount = wRule.unpack("H")

                    subLogger2.debug(
                        ('Vxxxx', (backCount, ), "Backtrack count is %d"))

                    if wRule.length() < 2 * backCount:
                        subLogger2.error(('V0382', (
                        ), "The Backtrack classes are missing or incomplete."))

                        return None

                    tBack = ClassTuple(reversed(wRule.group("H", backCount)))

                    subLogger2.debug(('Vxxxx', (tBack, ),
                                      "Backtrack classes (reversed) are %s"))

                    if wRule.length() < 2:
                        subLogger2.error(
                            ('V0383', (),
                             "The InputGlyphCount is missing or incomplete."))

                        return None

                    inCount = wRule.unpack("H") - 1

                    subLogger2.debug(
                        ('Vxxxx', (inCount + 1, ), "Input count is %d"))

                    if wRule.length() < 2 * inCount:
                        subLogger2.error(
                            ('V0384', (),
                             "The Input classes are missing or incomplete."))

                        return None

                    tIn = ClassTuple((firstClassIndex, ) +
                                     wRule.group("H", inCount))

                    subLogger2.debug(
                        ('Vxxxx', (tIn, ), "Input classes are %s"))

                    if wRule.length() < 2:
                        subLogger2.error((
                            'V0385', (),
                            "The LookaheadGlyphCount is missing or incomplete."
                        ))

                        return None

                    lookCount = wRule.unpack("H")

                    subLogger2.debug(
                        ('Vxxxx', (lookCount, ), "Lookahead count is %d"))

                    if wRule.length() < 2 * lookCount:
                        subLogger2.error(('V0386', (
                        ), "The Lookahead classes are missing or incomplete."))

                        return None

                    tLook = ClassTuple(wRule.group("H", lookCount))

                    subLogger2.debug(
                        ('Vxxxx', (tLook, ), "Lookahead classes are %s"))

                    if wRule.length() < 2:
                        subLogger2.error(
                            ('V0387', (),
                             "The lookup count is missing or incomplete."))

                        return None

                    posCount = wRule.unpack("H")

                    subLogger2.debug(
                        ('Vxxxx', (posCount, ), "Action count is %d"))

                    key = Key([tBack, tIn, tLook], ruleOrder=ruleOrder)

                    obj = fvw(wRule,
                              count=posCount,
                              fixupList=fixupList,
                              logger=subLogger2,
                              **kwArgs)

                    if obj is None:
                        return None

                    r[key] = obj

            else:
                subLogger.debug(('Vxxxx', (), "Set offset is zero"))

        # Now that we have the keys we can reconcile

        okToProceed, covSet = coverageutilities.reconcile(covTable,
                                                          {k[1]
                                                           for k in r}, [cdIn],
                                                          logger=logger,
                                                          **kwArgs)

        r.coverageExtras.update(covSet - set(cdIn))

        if not okToProceed:
            r.clear()

        return r
Ejemplo n.º 16
0
    def fromValidatedFontWorkerSource(cls, fws, **kwArgs):
        """
        Creates and returns a new PSChainClass from the specified
        FontWorkerSource.

        >>> logger = utilities.makeDoctestLogger("FW_test")
        >>> obj = PSChainClass.fromValidatedFontWorkerSource(
        ...   _test_FW_fws2,
        ...   namer = _test_FW_namer,
        ...   forGPOS = True,
        ...   lookupDict = _test_FW_lookupDict,
        ...   logger = logger,
        ...   editor = {})
        FW_test.pschainclass - WARNING - line 20 -- unexpected token: foo
        FW_test.pschainclass - WARNING - line 22 -- invalid backtrack class: 4
        FW_test.pschainclass - WARNING - line 22 -- invalid input class: 7
        FW_test.pschainclass - WARNING - line 22 -- invalid lookahead class: 6
        FW_test.pschainclass - WARNING - line 0 -- did not find matching 'subtable end/lookup end'
        >>> obj.pprint()
        Key((ClassTuple((2, 1, 0)), ClassTuple((0, 5)), ClassTuple((7, 8, 9, 0))), ruleOrder=0):
          Effect #1:
            Sequence index: 0
            Lookup:
              3:
                FUnit adjustment to horizontal advance: 678
          Effect #2:
            Sequence index: 0
            Lookup:
              3:
                FUnit adjustment to horizontal advance: 901
        Class definition table (backtrack):
          1: 1
          2: 2
          3: 3
        Class definition table (input):
          1: 4
          2: 5
          3: 6
        Class definition table (lookahead):
          1: 7
          2: 8
          3: 9
        """

        logger = kwArgs.pop('logger', logging.getLogger())
        logger = logger.getChild("pschainclass")
        terminalStrings = ('subtable end', 'lookup end')
        startingLineNumber = fws.lineNumber

        # place-holders
        classDefBacktrack = classdef.ClassDef()
        classDefInput = classdef.ClassDef()
        classDefLookahead = classdef.ClassDef()

        ruleOrders = {}
        lookupGroups = {}
        stringKeys = {}

        for line in fws:
            if line.lower() in terminalStrings:
                r = cls(lookupGroups,
                        classDefBacktrack=classDefBacktrack,
                        classDefInput=classDefInput,
                        classDefLookahead=classDefLookahead)

                return r

            if len(line) > 0:
                tokens = [x.strip() for x in line.split('\t')]
                fVFWS = classdef.ClassDef.fromValidatedFontWorkerSource

                if tokens[0].lower() == 'backtrackclass definition begin':
                    classDefBacktrack = fVFWS(fws, logger=logger, **kwArgs)
                    cdBackSet = set(classDefBacktrack.values())

                elif tokens[0].lower() == 'class definition begin':
                    classDefInput = fVFWS(fws, logger=logger, **kwArgs)
                    cdInputSet = set(classDefInput.values())

                elif tokens[0].lower() == 'lookaheadclass definition begin':
                    classDefLookahead = fVFWS(fws,
                                              logger=logger,
                                              dbg=True,
                                              **kwArgs)
                    cdLookSet = set(classDefLookahead.values())

                elif tokens[0].lower() == 'class-chain':
                    CT = pschainclass_classtuple.ClassTuple
                    classTuple1 = CT()
                    classTuple2 = CT()
                    classTuple3 = CT()
                    classesOK = True

                    if tokens[1] != '':
                        try:
                            classList1 = [int(x) for x in tokens[1].split(',')]
                            classList1.reverse(
                            )  # backtrack goes in reverse order
                        except:
                            logger.warning((
                                'Vxxxx', (fws.lineNumber, tokens[1]),
                                'line %d -- invalid backtrack definition: %s'))

                            classesOK = False

                        for classNum in classList1:
                            if classNum == 0:
                                continue

                            if not classNum in cdBackSet:
                                logger.warning(
                                    ('V0962', (fws.lineNumber, classNum),
                                     'line %d -- invalid backtrack class: %d'))

                                classesOK = False

                        classTuple1 = CT(classList1)

                    if tokens[2] != '':
                        try:
                            classList2 = [int(x) for x in tokens[2].split(',')]
                        except ValueError:
                            logger.warning(
                                ('Vxxxx', (fws.lineNumber, tokens[2]),
                                 'line %d -- invalid input definition: %s'))

                            classesOK = False

                        for classNum in classList2:
                            if classNum == 0:
                                continue

                            if not classNum in cdInputSet:
                                logger.warning(
                                    ('V0962', (fws.lineNumber, classNum),
                                     'line %d -- invalid input class: %d'))

                                classesOK = False

                        classTuple2 = CT(classList2)

                    if tokens[3] != '':
                        try:
                            classList3 = [int(x) for x in tokens[3].split(',')]
                        except ValueError:
                            logger.warning((
                                'Vxxxx', (fws.lineNumber, tokens[3]),
                                'line %d -- invalid lookahead definition: %s'))

                            classesOK = False

                        for classNum in classList3:
                            if classNum == 0:
                                continue

                            if not classNum in cdLookSet:
                                logger.warning(
                                    ('V0962', (fws.lineNumber, classNum),
                                     'line %d -- invalid lookahead class: %d'))

                                classesOK = False

                        classTuple3 = CT(classList3)

                    if not classesOK:
                        continue

                    lookupList = []

                    for effect in tokens[4:]:
                        effectTokens = [x.strip() for x in effect.split(',')]
                        sequenceIndex = int(effectTokens[0]) - 1
                        lookupName = effectTokens[1]

                        lookupList.append(
                            pslookuprecord.PSLookupRecord(
                                sequenceIndex,
                                lookup.Lookup.fromValidatedFontWorkerSource(
                                    fws, lookupName, logger=logger, **kwArgs)))

                    stringKey = "(%s), (%s), (%s)" % (",".join([
                        str(ci) for ci in classTuple1[::-1]
                    ]), ",".join([str(ci) for ci in classTuple2]), ",".join(
                        [str(ci) for ci in classTuple3]))

                    if stringKey in stringKeys:
                        logger.warning(('Vxxxx', (
                            fws.lineNumber, stringKey, stringKeys[stringKey]
                        ), "line %d -- context '%s' previously defined at line %d"
                                        ))

                    else:
                        stringKeys[stringKey] = fws.lineNumber

                        key = pschainclass_key.Key(
                            [classTuple1, classTuple2, classTuple3])

                        ruleOrder = ruleOrders.get(classTuple2[0], 0)
                        key.ruleOrder = ruleOrder
                        ruleOrders[classTuple2[0]] = ruleOrder + 1
                        lookupGroup = pslookupgroup.PSLookupGroup(lookupList)
                        lookupGroups[key] = lookupGroup

                else:
                    logger.warning(('V0960', (fws.lineNumber, tokens[0]),
                                    'line %d -- unexpected token: %s'))

        logger.warning(
            ('V0958', (startingLineNumber, "/".join(terminalStrings)),
             "line %d -- did not find matching '%s'"))

        r = cls(lookupGroups,
                classDefBacktrack=classDefBacktrack,
                classDefInput=classDefInput,
                classDefLookahead=classDefLookahead)

        return r
Ejemplo n.º 17
0
# -----------------------------------------------------------------------------

#
# Test code
#

if 0:
    def __________________(): pass

if __debug__:
    from fontio3.utilities import namer
    from fontio3.opentype.fontworkersource import FontWorkerSource
    from io import StringIO
    
    c1 = classdef.ClassDef({5: 1, 6: 1, 7: 2, 15: 1})
    c2 = classdef.ClassDef({20: 1, 21: 1, 22: 1})
    pv = pairvalues._testingValues
    kv = pairclasses_key._testingValues
    
    _testingValues = (
        PairClasses(
            {kv[0]: pv[0], kv[1]: pv[1], kv[2]: pv[2]},
            classDef1 = c1,
            classDef2 = c2),)
    
    del c1, c2, pv

    _test_FW_namer = namer.Namer(None)
    _test_FW_namer._nameToGlyph = {
        'A': 2,
Ejemplo n.º 18
0
    def fromwalker(cls, w, **kwArgs):
        """
        Creates and returns a Format2 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 == Format2.frombytes(
        ...   obj.binaryString(),
        ...   coverage = coverage.Coverage())
        True
        """

        headerAdj = 12
        rowBytes = w.unpack("L")
        nCols = rowBytes // 2
        leftOffset, rightOffset, arrayBase = w.unpack("3L")

        # Determine nRows, by finding the largest offset present in the left
        # class table.

        wLeft = w.subWalker(leftOffset - headerAdj)
        leftLookup = lookup.Lookup.fromwalker(wLeft)

        leftCD = classdef.ClassDef(
            {i: j // rowBytes
             for i, j in leftLookup.items()})

        nRows = 1 + max(leftCD.values())
        wRight = w.subWalker(rightOffset - headerAdj)
        rightLookup = lookup.Lookup.fromwalker(wRight)

        rightCD = classdef.ClassDef(
            {i: j // 2
             for i, j in rightLookup.items()})

        wArray = w.subWalker(arrayBase - headerAdj)
        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
Ejemplo n.º 19
0
    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
Ejemplo n.º 20
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
Ejemplo n.º 21
0
    def fromvalidatedwalker(cls, w, **kwArgs):
        """
        Creates and returns a Format3 object from the specified walker, doing
        source validation.
        
        >>> s = _testingValues[1].binaryString()
        >>> logger = utilities.makeDoctestLogger("format3_fvw")
        >>> fvb = Format3.fromvalidatedbytes
        >>> cv = _testingValues[1].coverage.__copy__()
        >>> d = {'logger': logger, 'coverage': cv, 'fontGlyphCount': 41}
        >>> obj = fvb(s, **d)
        format3_fvw.format3 - DEBUG - Walker has 106 remaining bytes.
        >>> obj == _testingValues[1]
        True
        
        >>> fvb(s[:3], **d)
        format3_fvw.format3 - DEBUG - Walker has 3 remaining bytes.
        format3_fvw.format3 - ERROR - Insufficient bytes.
        
        >>> d['fontGlyphCount'] = 100
        >>> fvb(s, **d)
        format3_fvw.format3 - DEBUG - Walker has 106 remaining bytes.
        format3_fvw.format3 - ERROR - The font has 100 glyphs, but the glyphCount field is 41.
        """

        fgc = kwArgs.pop('fontGlyphCount', 0x10000)
        logger = kwArgs.pop('logger', logging.getLogger())
        logger = logger.getChild("format3")

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

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

        glyphCount, kCount, lCount, rCount, flags = w.unpack("H4B")

        if glyphCount != fgc:
            logger.error(
                ('V0617', (fgc, glyphCount),
                 "The font has %d glyphs, but the glyphCount field is %d."))

            return None

        if not all([kCount, lCount, rCount]):
            logger.warning(
                ('V0618', (),
                 "One or more of the kernValueCount, leftClassCount, and "
                 "rightClassCount is zero. The table is thus empty."))

            return cls({}, **utilities.filterKWArgs(cls, kwArgs))

        if flags:
            logger.warning(
                ('V0619', (flags, ), "The flags should be zero, but are %d."))

        if w.length() < (2 * (kCount + glyphCount)) + (lCount * rCount):
            logger.error(
                ('V0620', (), "The arrays are missing or incomplete."))

            return None

        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