def buildBinary(self, w, **kwArgs):
        """
        Adds the binary data for the SubtableGlyphCoverageSet object to the
        specified LinkedWriter.

        The keyword arguments are:
            stakeValue      The stake value to be used at the start of this
                            output.
            fontGlyphCount  Number of glyphs in the font.
        >>> s = SubtableGlyphCoverageSet({1, 2, 3, 42})
        >>> fontGlyphCount = 43
        >>> w = LinkedWriter()
        >>> stakeValue = w.getNewStake()
        >>> kwArgs = {'fontGlyphCount': fontGlyphCount, 'stakeValue': stakeValue}
        >>> s.buildBinary(w, **kwArgs)
        >>> hexdump(w.binaryString())
               0 | 0E00 0000 0004 0000                      |........        |
        """
        if 'stakeValue' in kwArgs:
            stakeValue = kwArgs.pop('stakeValue')
            w.stakeCurrentWithValue(stakeValue)
        # else:
        #     stakeValue = w.stakeCurrent()

        numGlyphs = getFontGlyphCount(**kwArgs)
        bytesPerSubtable = getBytesPerSubtableCoverageArray(**kwArgs)
        data = [0] * bytesPerSubtable
        for glyph_index in range(numGlyphs):
            if glyph_index in self:
                data[glyph_index // 8] |= (1 << (glyph_index & 0x07))
        w.addGroup('B', data)
示例#2
0
def _validate_sxHeight(value, **kwArgs):
    logger = kwArgs['logger']
    editor = kwArgs['editor']
    numGlyphs = utilities.getFontGlyphCount(**kwArgs)

    if (editor is None) or (not editor.reallyHas(b'cmap')):
        logger.error(('V0553', (),
                      "Unable to validate sxHeight because the Editor and/or "
                      "Cmap are missing or empty."))

        return False

    if value < 0:
        logger.error(('Vxxxx', (value, ), "sxHeight (%d) is less than zero!"))

        return False

    umap = editor.cmap.getUnicodeMap()
    gidofx = umap.get(0x0078)

    if gidofx is not None:
        if not 0 <= gidofx < numGlyphs:
            logger.error(
                ('Vxxxx', (gidofx, ),
                 "sxHeight value could not be validated because the glyph "
                 "corresponding to 'x' (%d) is out of range for the font."))

            return False

        if editor.reallyHas(b'glyf'):
            gObj = editor.glyf[gidofx]

        elif editor.reallyHas(b'CFF '):
            gObj = editor[b'CFF '][gidofx]

        else:
            logger.warning(
                ('Vxxxx', (value, ),
                 "No 'glyf' or 'CFF ' table present; could not validate "
                 "sxHeight value (%d)"))

            return True

        if gObj and gObj.bounds:
            actual = gObj.bounds.yMax
        else:
            actual = None

        if value != actual:
            logger.warning(
                ('Vxxxx', (value, actual),
                 "sxHeight value (%d) does not match yMax of 'x' (%s)"))

    else:
        logger.warning(
            ('Vxxxx', (value, ),
             "sxHeight value (%d) could not be validated because no "
             "glyph for 'x' is present in the font."))

    return True
示例#3
0
    def frompairclasses(cls, pcObj, **kwArgs):
        """
        Given a PairClasses object, flatten it into a PairGlyphs object.
        
        The useSpecialClass0 keyword (default True) will take a class value of
        zero and map it to the special glyph code None, in order to keep the
        size of the resulting conversion reasonable. If the keyword is False
        then all the class 0 entries will be skipped.
        
        However, if you want a full expansion, including all the exceptions,
        then specify the fullExpansion=True keyword. Note that in this case you
        will also have to provide either an editor or a fontGlyphCount
        keyword argument, so that the class 0 expansions can be done.
        
        If both useSpecialClass0 and fullExpansion are True, useSpecialClass0
        wins.
        """

        r = cls()
        cd1 = utilities.invertDictFull(pcObj.classDef1, asSets=True)
        cd2 = utilities.invertDictFull(pcObj.classDef2, asSets=True)
        K = pairglyphs_key.Key
        useSpecial = kwArgs.get('useSpecialClass0', True)

        if kwArgs.get('fullExpansion', False):
            fullExpansion = True
            c1Set = pcObj.coverageExtras
            fgc = utilities.getFontGlyphCount(**kwArgs)
            c2Set = set(range(fgc))
            excludes = {g for c, s in cd2.items() if c for g in s}
            c2Set -= excludes

        else:
            fullExpansion = False

        for cKey, cValue in pcObj.items():
            if cKey[0]:
                it1 = cd1[cKey[0]]
            elif useSpecial:
                it1 = [None]
            elif fullExpansion:
                it1 = c1Set
            else:
                it1 = []

            for g1 in it1:
                if cKey[1]:
                    it2 = cd2[cKey[1]]
                elif useSpecial:
                    it2 = [None]
                elif fullExpansion:
                    it2 = c2Set
                else:
                    it2 = []

                for g2 in it2:
                    r[K([g1, g2])] = cValue

        return r
示例#4
0
    def fromvalidatedwalker(cls, w, **kwArgs):
        """
        Creates and returns a new GlyphRecord object from the specified walker,
        doing validation.
        
        >>> logger = utilities.makeDoctestLogger("strike")
        >>> fvb = Strike.fromvalidatedbytes
        >>> GR = glyphrecord.GlyphRecord
        >>> rec1 = GR(15, -35, 'tiff', b"Some data here")
        >>> rec2 = GR(0, 16, 'tiff', b"ABC")
        >>> obj = Strike({4: rec1, 5: rec2})
        >>> bs = obj.binaryString(fontGlyphCount=10)
        >>> obj = fvb(bs, fontGlyphCount=10, logger=logger)
        strike.strike - DEBUG - Walker has 77 remaining bytes.
        strike.strike.glyph 4.glyphrecord - DEBUG - Walker has 22 remaining bytes.
        strike.strike.glyph 5.glyphrecord - DEBUG - Walker has 11 remaining bytes.
        """

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

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

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

        baseOffset = w.getOffset()
        fgc = utilities.getFontGlyphCount(**kwArgs)

        if fgc is None:
            logger.error(
                ('V1017', (), "Unable to ascertain the font's glyph count."))

            return None

        if w.length() < (4 * (fgc + 1)):
            logger.error(('V0004', (), "Insufficient bytes."))
            return None

        offsets = w.group("L", fgc + 1)
        fw = glyphrecord.GlyphRecord.fromvalidatedwalker
        r = cls()

        for i in range(fgc):
            if offsets[i] == offsets[i + 1]:
                continue

            wSub = w.subWalker(offsets[i] - 4,
                               newLimit=baseOffset + offsets[i + 1] - 4)

            subLogger = logger.getChild("glyph %d" % (i, ))
            r[i] = fw(wSub, logger=subLogger, **kwArgs)

            if r[i] is None:
                return None

        return r
示例#5
0
    def fromeditor(cls, e, **kwArgs):
        """
        Synthesize a new GlyphClassTable from the information in the specified
        Editor. This includes the 'maxp', 'cmap', 'GSUB', 'GPOS', and 'glyf'
        (for TrueType fonts) tables.
        """

        kwArgs['editor'] = e
        numGlyphs = utilities.getFontGlyphCount(**kwArgs)
        r = cls({n: 1 for n in range(numGlyphs)})
        allOTGlyphs = set()

        if e.reallyHas(b'hmtx'):
            zeroAdvs = {i for i, obj in e.hmtx.items() if not obj.advance}
        else:
            zeroAdvs = set()

        if e.reallyHas(b'GPOS'):
            gposMarks = cls._fromeditor_marks_from_GPOS(e.GPOS)
            allOTGlyphs.update(e.GPOS.gatheredInputGlyphs())
            allOTGlyphs.update(e.GPOS.gatheredOutputGlyphs())

        else:
            gposMarks = set()

        if e.reallyHas(b'GSUB'):
            gsubLigs = cls._fromeditor_ligs_from_GSUB(e.GSUB)
            allOTGlyphs.update(e.GSUB.gatheredInputGlyphs())
            allOTGlyphs.update(e.GSUB.gatheredOutputGlyphs())

        else:
            gsubLigs = set()

        if e.reallyHas(b'glyf') and e.reallyHas(b'cmap'):
            compGlyphs = cls._fromeditor_comps_from_glyf(e.glyf, e.cmap)
        else:
            compGlyphs = set()

        # Now that the various data sources have been gathered, update the
        # new object.

        for i in zeroAdvs & gposMarks:
            r[i] = 3  # mark

        for i in gsubLigs:
            r[i] = 2  # ligature

        for i in compGlyphs - allOTGlyphs:
            r[i] = 4  # component only

        return r
def getBytesPerSubtableCoverageArray(**kwArgs):
    """
    Returns the number of bytes for a subtable glyph coverage array based
    on the number of glyphs in the font, includes padding bytes if needed
    to ensure a four-byte boundary.
    >>> kwArgs = {'fontGlyphCount': 27}
    >>> getBytesPerSubtableCoverageArray(**kwArgs)
    4
    """
    numGlyphs = getFontGlyphCount(**kwArgs)
    # Each coverage bitfield is (numGlyphs + CHAR_BIT - 1) / CHAR_BIT bytes
    # in size...
    bytesPerSubtable = (numGlyphs + 7) // 8
    # ...padded to a four-byte boundary.
    bytesPerSubtable = ((bytesPerSubtable + 3) // 4) * 4
    return bytesPerSubtable
    def fromvalidatedwalker(cls, w, **kwArgs):
        """
        Creates and returns a new SubtableGlyphCoverageSet object from the
        specified walker, doing source validation.

        >>> wb = StringWalkerBit(bytes.fromhex("0E 00 00 00 00 04 00 00"))
        >>> fontGlyphCount = 43
        >>> logger = makeDoctestLogger("fvw")
        >>> kwArgs = {'fontGlyphCount': fontGlyphCount, 'logger': logger}
        >>> SubtableGlyphCoverageSet.fromvalidatedwalker(w=wb, **kwArgs)
        fvw - DEBUG - Walker has 8 remaining bytes.
        fvw - DEBUG - Walker has 0 remaining bytes.
        SubtableGlyphCoverageSet({1, 2, 3, 42})
        >>> wb2 = StringWalkerBit(bytes.fromhex("0E 00 00 00 00"))
        >>> SubtableGlyphCoverageSet.fromvalidatedwalker(w=wb2, **kwArgs)
        fvw - DEBUG - Walker has 5 remaining bytes.
        fvw - ERROR - Insufficient bytes -- need 8, have 5.
        """

        logger = kwArgs.pop('logger', logging.getLogger())
        logger.debug((
            'V0001',
            (w.length(),),
            "Walker has %d remaining bytes."))
        r = cls()
        numGlyphs = getFontGlyphCount(**kwArgs)
        bytesPerSubtable = getBytesPerSubtableCoverageArray(**kwArgs)

        bytesAvailable = w.length()
        bytesNeeded = bytesPerSubtable
        if bytesAvailable < bytesNeeded:
            logger.error(('V0004', (bytesNeeded, bytesAvailable),
                          "Insufficient bytes -- need %d, have %d."))
            return None

        data = w.group('B', bytesPerSubtable)
        for glyph_index in range(numGlyphs):
            # To determine if a particular glyph is covered by the subtable,
            # calculate coverageBitfield[glyph/CHAR_BIT] & (1 << (glyph & (CHAR_BIT-1))).
            # If this is non-zero, the glyph is covered.
            if data[glyph_index // 8] & (1 << (glyph_index & 0x07)) != 0:
                r.add(glyph_index)
        logger.debug((
            'V0001',
            (w.length(),),
            "Walker has %d remaining bytes."))
        return r
示例#8
0
    def buildBinary(self, w, **kwArgs):
        """
        Adds the binary content for the Strike to the specified writer.
        
        >>> GR = glyphrecord.GlyphRecord
        >>> rec1 = GR(15, -35, 'tiff', b"Some data here")
        >>> rec2 = GR(0, 16, 'tiff', b"ABC")
        >>> obj = Strike({4: rec1, 5: rec2})
        >>> utilities.hexdump(obj.binaryString(fontGlyphCount=10))
               0 | 0000 0030 0000 0030  0000 0030 0000 0030 |...0...0...0...0|
              10 | 0000 0030 0000 0046  0000 0051 0000 0051 |...0...F...Q...Q|
              20 | 0000 0051 0000 0051  0000 0051 000F FFDD |...Q...Q...Q....|
              30 | 7469 6666 536F 6D65  2064 6174 6120 6865 |tiffSome data he|
              40 | 7265 0000 0010 7469  6666 4142 43        |re....tiffABC   |
        """

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

        # The "+2" in the following accounts for the extra offset needed at
        # the end, to compute the size of the last entry.

        maxGlyph = utilities.getFontGlyphCount(**kwArgs) - 1
        stakes = [w.getNewStake() for i in range(maxGlyph + 2)]

        for stake in stakes:

            # The offsetByteDelta in the following allows us to bypass the
            # ppem and resolution here, leaving them for the parent Sbix
            # object to handle.

            w.addUnresolvedOffset("L", stakeValue, stake, offsetByteDelta=4)

        for glyphIndex in range(maxGlyph + 1):
            stake = stakes[glyphIndex]

            if (glyphIndex not in self) or (self[glyphIndex] is None):
                w.stakeCurrentWithValue(stake)
            else:
                self[glyphIndex].buildBinary(w, stakeValue=stake)

        w.stakeCurrentWithValue(stakes[-1])
 def fromwalker(cls, w, **kwArgs):
     """
     Creates and returns a SubtableGlyphCoverageSet object from the
     specified walker.
     >>> wb = StringWalkerBit(bytes.fromhex("0E 00 00 00 00 04 00 00"))
     >>> fontGlyphCount = 43
     >>> kwArgs = {'fontGlyphCount': fontGlyphCount}
     >>> SubtableGlyphCoverageSet.fromwalker(w=wb, **kwArgs)
     SubtableGlyphCoverageSet({1, 2, 3, 42})
     """
     r = cls()
     numGlyphs = getFontGlyphCount(**kwArgs)
     bytesPerSubtable = getBytesPerSubtableCoverageArray(**kwArgs)
     data = w.group('B', bytesPerSubtable)
     for glyph_index in range(numGlyphs):
         # To determine if a particular glyph is covered by the subtable,
         # calculate coverageBitfield[glyph/CHAR_BIT] & (1 << (glyph & (CHAR_BIT-1))).
         # If this is non-zero, the glyph is covered.
         if data[glyph_index // 8] & (1 << (glyph_index & 0x07)) != 0:
             r.add(glyph_index)
     return r
示例#10
0
    def fromwalker(cls, w, **kwArgs):
        """
        Creates and returns a new GlyphRecord object from the specified walker.
        
        >>> GR = glyphrecord.GlyphRecord
        >>> rec1 = GR(15, -35, 'tiff', b"Some data here")
        >>> rec2 = GR(0, 16, 'tiff', b"ABC")
        >>> obj = Strike({4: rec1, 5: rec2})
        >>> bs = obj.binaryString(fontGlyphCount=10)
        >>> Strike.frombytes(bs, fontGlyphCount=10).pprint()
        4:
          originOffsetX: 15
          originOffsetY: -35
          graphicType: tiff
          data: b'Some data here'
        5:
          originOffsetX: 0
          originOffsetY: 16
          graphicType: tiff
          data: b'ABC'
        """

        baseOffset = w.getOffset()
        fgc = utilities.getFontGlyphCount(**kwArgs)
        offsets = w.group("L", fgc + 1)
        fw = glyphrecord.GlyphRecord.fromwalker
        r = cls()

        for i in range(fgc):
            if offsets[i] == offsets[i + 1]:
                continue

            wSub = w.subWalker(offsets[i] - 4,
                               newLimit=baseOffset + offsets[i + 1] - 4)

            r[i] = fw(wSub, **kwArgs)

        return r
示例#11
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
示例#12
0
    def fromvalidatedwalker(cls, w, **kwArgs):
        """
        Like fromwalker, but with validation.
        
        The following keyword arguments are supported:
        
            fontGlyphCount      The glyph count from the 'maxp' table.
                                This is required.

            dataTable           A TSIDat object (typically TSI1). This
                                is optional, but if not included, only
                                default glyphnames will be used, and any
                                custom names defined in a Control Program
                                will be ignored.
            
            logger              A logger to which notices will be posted. This
                                is optional; the default logger will be used if
                                this is not provided.
                                
        >>> fvb=TSI5.fromvalidatedbytes
        >>> l=utilities.makeDoctestLogger("test")
        >>> s = utilities.fromhex("00 01 00 02 00 08 00 09 00 FF")
        >>> obj= fvb(s, fontGlyphCount=5, logger=l, dataTable=_testingDatTbl)
        test.TSI5 - DEBUG - Walker has 10 remaining bytes.
        test.TSI5 - INFO - There are 5 entries.
        test.TSI5 - ERROR - Group index 255 for glyph 4 is beyond the range of defined glyph groups.
        >>> obj[2], obj[3]
        ('TestGroup1', 'TestGroup2')
        """

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

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

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

        fontGlyphCount = utilities.getFontGlyphCount(**kwArgs)
        count = int(w.length() / 2)

        if count != fontGlyphCount:
            logger.warning(
                ('V0482', (), "Length of the table is not consistent with "
                 "font's glyph count."))

        else:
            logger.info(('V0483', (count, ), "There are %d entries."))

        dataTable = kwArgs.pop('dataTable', None)
        groups = _getGroupNames(dataTable)
        d = {}

        if count:
            gc = w.group("H", count)

            for k, v in enumerate(gc):
                if v < len(groups):
                    d[k] = groups[v]

                else:
                    logger.error(
                        ('Vxxxx', (v, k),
                         "Group index %d for glyph %d is beyond the range "
                         "of defined glyph groups."))

                    d[k] = groups[0]

        return cls(d, groupnames=groups)
示例#13
0
 def fromvalidatedwalker(cls, w, **kwArgs):
     """
     Creates and returns a new Ligature object from the specified walker,
     doing source validation. The walker should be based at the state table
     start (i.e. at numClasses).
     
     >>> origObj = _makeObj().trimmedToValidEntries()
     >>> s = origObj.binaryString()
     >>> logger = utilities.makeDoctestLogger("ligature_fvw")
     >>> fvb = Ligature.fromvalidatedbytes
     >>> d = {
     ...   'coverage': origObj.coverage,
     ...   'maskValue': 0x00000080,
     ...   'logger': logger,
     ...   'fontGlyphCount': 0x1000}
     >>> obj = fvb(s, **d)
     ligature_fvw.ligature - DEBUG - Walker has 514 remaining bytes.
     ligature_fvw.ligature.lookup_aat - DEBUG - Walker has 32 remaining bytes.
     ligature_fvw.ligature - WARNING - The entry for state 2, class 6 does ligature substitution, but does not lead back to the ground state. This might be problematic.
     ligature_fvw.ligature - WARNING - The entry for state 5, class 6 does ligature substitution, but does not lead back to the ground state. This might be problematic.
     ligature_fvw.ligature - WARNING - The entry for state 6, class 7 does ligature substitution, but does not lead back to the ground state. This might be problematic.
     ligature_fvw.ligature - WARNING - The entry for state 2, class 6 does ligature substitution, but does not lead back to the ground state. This might be problematic.
     ligature_fvw.ligature - WARNING - The entry for state 2, class 6 does ligature substitution, but does not lead back to the ground state. This might be problematic.
     ligature_fvw.ligature - WARNING - The entry for state 2, class 6 does ligature substitution, but does not lead back to the ground state. This might be problematic.
     ligature_fvw.ligature - WARNING - The entry for state 2, class 6 does ligature substitution, but does not lead back to the ground state. This might be problematic.
     ligature_fvw.ligature - WARNING - The entry for state 2, class 6 does ligature substitution, but does not lead back to the ground state. This might be problematic.
     ligature_fvw.ligature.namestash - DEBUG - Walker has 486 remaining bytes.
     ligature_fvw.ligature.clstable - DEBUG - Walker has 32 remaining bytes.
     >>> obj == origObj
     True
     
     >>> fvb(s[:19], **d)
     ligature_fvw.ligature - DEBUG - Walker has 19 remaining bytes.
     ligature_fvw.ligature - ERROR - Insufficient bytes.
     
     >>> fvb(s[:39], **d)
     ligature_fvw.ligature - DEBUG - Walker has 39 remaining bytes.
     ligature_fvw.ligature - ERROR - One or more offsets to state table components are outside the bounds of the state table itself.
     """
     
     logger = kwArgs.pop('logger', logging.getLogger())
     logger = logger.getChild('ligature')
     
     logger.debug((
       'V0001',
       (w.length(),),
       "Walker has %d remaining bytes."))
     
     analyzer = ligatureanalyzer.Analyzer(w.subWalker(0, relative=True))
     analyzer.analyze(logger=logger, **kwArgs)
     
     if analyzer.analysis is None:
         return None
     
     # Note that some of the size and other validation was already done in
     # the analyze() call, and is not reduplicated here.
     
     numClasses, oCT, oSA, oET, oLA, oCP, oLG = w.unpack("7L")
     offsets = (oCT, oSA, oET, oLA, oCP, oLG)
     
     wCT, wSA, wET, wLA, wCP, wLG = stutils.offsetsToSubWalkers(
       w.subWalker(0),
       *offsets)
     
     numStates = analyzer.numStates
     
     nsObj = namestash.NameStash.readormake_validated(
       w,
       offsets,
       numStates,
       numClasses,
       logger = logger)
     
     if nsObj is None:
         return None
     
     stateNames = nsObj.allStateNames()
     classNames = nsObj.allClassNames()
     fgc = utilities.getFontGlyphCount(**kwArgs)
     
     classTable = classtable.ClassTable.fromvalidatedwalker(
       wCT,
       classNames = classNames,
       logger = logger,
       fontGlyphCount = fgc)
     
     if classTable is None:
         return None
     
     kwArgs.pop('classTable', None)
     
     r = cls(
       {},
       classTable = classTable,
       **utilities.filterKWArgs(cls, kwArgs))
     
     entryPool = {}  # entryIndex -> Entry object
     
     for stateIndex, rawStateRow in enumerate(analyzer.stateArray):
         thisRow = staterow_ligature.StateRow()
         
         for classIndex, rawEntryIndex in enumerate(rawStateRow):
             if rawEntryIndex not in entryPool:
                 t = analyzer.entryTable[rawEntryIndex]
                 newStateIndex, flags, laIndex = t
                 
                 entryPool[rawEntryIndex] = entry_ligature.Entry(
                   newState = stateNames[newStateIndex],
                   push = bool(flags & 0x8000),
                   noAdvance = bool(flags & 0x4000))
             
             e = entryPool[rawEntryIndex]
             d = analyzer.finalDicts.get((stateIndex, classIndex), None)
             
             if d is not None:
                 for inGlyphs, outGlyphs in d.items():
                     gti = glyphtuple.GlyphTupleInput(inGlyphs)
                     
                     if gti not in e.actions:
                         gto = glyphtuple.GlyphTupleOutput(outGlyphs)
                         e.actions[gti] = gto
             
             thisRow[classNames[classIndex]] = e
         
         r[stateNames[stateIndex]] = thisRow
     
     return r.trimmedToValidEntries()
示例#14
0
    def fromvalidatedwalker(cls, w, **kwArgs):
        """
        Creates and returns a new Contextual object from the specified walker,
        doing source validation.
        
        >>> s = _testingValues[0].binaryString()
        >>> logger = utilities.makeDoctestLogger("contextual_fvw")
        >>> fvb = Contextual.fromvalidatedbytes
        >>> d = {
        ...   'fontGlyphCount': 1000,
        ...   'coverage': _testingValues[0].coverage,
        ...   'maskValue': 0x10000000,
        ...   'logger': logger}
        >>> obj = fvb(s, **d)
        contextual_fvw.contextual - DEBUG - Walker has 160 remaining bytes.
        contextual_fvw.contextual.classmap.lookup_aat - DEBUG - Walker has 30 remaining bytes.
        contextual_fvw.contextual.classmap.lookup_aat.binsearch.binsrch header - DEBUG - Walker has 28 remaining bytes.
        contextual_fvw.contextual.per-glyph 0.lookup_aat - DEBUG - Walker has 14 remaining bytes.
        contextual_fvw.contextual.namestash - DEBUG - Walker has 140 remaining bytes.
        contextual_fvw.contextual.clstable - DEBUG - Walker has 30 remaining bytes.
        contextual_fvw.contextual.clstable.binsearch.binsrch header - DEBUG - Walker has 28 remaining bytes.
        
        >>> obj == _testingValues[0]
        True
        
        >>> fvb(s[:5], **d)
        contextual_fvw.contextual - DEBUG - Walker has 5 remaining bytes.
        contextual_fvw.contextual - ERROR - Insufficient bytes.
        
        >>> fvb(s[:23], **d)
        contextual_fvw.contextual - DEBUG - Walker has 23 remaining bytes.
        contextual_fvw.contextual - ERROR - One or more offsets to state table components are outside the bounds of the state table itself.
        """

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

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

        analyzer = contextanalyzer.Analyzer(w.subWalker(0, relative=True))
        markAnalysis, currAnalysis = analyzer.analyze(logger=logger)

        if markAnalysis is None or currAnalysis is None:
            return None

        # Note that some of the size and other validation was already done in
        # the analyze() call, and is not reduplicated here.

        t = w.unpack("5L")
        numClasses, oCT, oSA, oET, oGT = t

        wCT, wSA, wET, wGT = stutils.offsetsToSubWalkers(
            w.subWalker(0), *t[1:])

        numStates = analyzer.numStates

        nsObj = namestash.NameStash.readormake_validated(w,
                                                         t[1:],
                                                         numStates,
                                                         numClasses,
                                                         logger=logger)

        if nsObj is None:
            return None

        stateNames = nsObj.allStateNames()
        classNames = nsObj.allClassNames()
        fgc = utilities.getFontGlyphCount(**kwArgs)

        classTable = classtable.ClassTable.fromvalidatedwalker(
            wCT, classNames=classNames, logger=logger, fontGlyphCount=fgc)

        if classTable is None:
            return None

        r = cls({},
                classTable=classTable,
                **utilities.filterKWArgs(cls, kwArgs))

        entryTable = analyzer.entryTable
        Entry = entry_contextual.Entry
        GD = glyphdict.GlyphDict
        StateRow = staterow_contextual.StateRow

        for stateIndex, rawState in enumerate(analyzer.stateArray):
            newRow = staterow_contextual.StateRow()

            for classIndex, entryIndex in enumerate(rawState):
                if entryIndex >= len(entryTable):
                    logger.error(
                        ('V0724', (stateNames[stateIndex],
                                   classNames[classIndex]),
                         "The state array cell for state '%s' and class '%s' "
                         "specifies an entry index that is out of range."))

                    return None

                newStateIndex, flags = entryTable[entryIndex][0:2]

                newEntry = Entry(
                    newState=stateNames[newStateIndex],
                    mark=bool(flags & 0x8000),
                    noAdvance=bool(flags & 0x4000),
                    markSubstitutionDict=GD(markAnalysis.get(entryIndex, {})),
                    currSubstitutionDict=GD(currAnalysis.get(entryIndex, {})))

                newRow[classNames[classIndex]] = newEntry

            r[stateNames[stateIndex]] = newRow

        return r
示例#15
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
示例#16
0
    def fromvalidatedwalker(cls, w, **kwArgs):
        """
        """

        if 'logger' in kwArgs:
            logger = kwArgs.pop('logger').getChild('gvar')
        else:
            logger = utilities.makeDoctestLogger('gvar')

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

        e = kwArgs['editor']

        if not e.reallyHas('fvar'):
            logger.error(
                ('V1037', (),
                 "Font has a 'gvar' table but no 'fvar' table; this is "
                 "not a valid state."))

            return None

        fvarObj = e.fvar
        ao = fvarObj.axisOrder
        r = cls(axisOrder=ao)
        origTableSize = w.length()

        if origTableSize < 20:
            logger.error(('V0004', (), "Insufficient bytes."))
            return None

        t = w.unpack("L2HL2HL")
        vers, axisCount, coordCount, coordOff, glyphCount, flags, dataOff = t
        logger.debug(('Vxxxx', (vers, ), "Version is 0x%08X"))
        logger.debug(('Vxxxx', (axisCount, ), "Axis count is %d"))
        logger.debug(('Vxxxx', (coordCount, ), "Global coord count is %d"))
        logger.debug(('Vxxxx', (coordOff, ), "Global coord offset is 0x%08X"))
        logger.debug(('Vxxxx', (glyphCount, ), "Glyph count is %d"))
        logger.debug(('Vxxxx', (flags, ), "Flags is 0x%04X"))
        logger.debug(('Vxxxx', (dataOff, ), "Data offset is 0x%08X"))

        if vers != r.VERSION:
            logger.error(('V1038', (
                r.VERSION,
                vers,
            ), "Unknown 'gvar' version; was expecting 0x%08X but "
                          "got 0x%08X instead."))

            return None

        if axisCount != len(ao):
            logger.error(
                ('V1039', (len(ao), axisCount),
                 "Mismatch between the axis counts in 'fvar' (%d) and "
                 "'gvar' (%d)."))

            return None

        if glyphCount != utilities.getFontGlyphCount(**kwArgs):
            logger.error(
                ('V1040', (),
                 "Mismatch between the 'maxp' and 'gvar' glyph counts."))

            return None

        if flags not in {0, 1}:
            logger.warning(
                ('V1041', (flags, ),
                 "The 'gvar' flags value is 0x%04X; this includes bits "
                 "that are not defined. These will be ignored."))

        # Check the offsets for reasonableness

        offsetSize = (4 if (flags & 1) else 2)

        if coordCount:
            globStop = coordOff + (coordCount * len(ao) * 2)
            lowBounds = 20 + (glyphCount + 1) * offsetSize

            if (coordOff < lowBounds) or (globStop > origTableSize):
                logger.error(('V1042', (
                ), "The 'gvar' global coordinates do not fit within the bounds "
                              "of the entire 'gvar' table."))

                return None

        if (dataOff < 20) or (dataOff >= origTableSize):
            logger.error(
                ('V1043', (),
                 "The 'gvar' variations data starting offset is outside "
                 "the bounds of the entire 'gvar' table"))

            return None

        # Read the global coordinates (if any)

        globalCoords = []

        if coordCount:
            wSub = w.subWalker(coordOff)
            fvw = axial_coordinates.AxialCoordinates.fromvalidatedwalker
            globalCoords = [None] * coordCount

            for i in range(coordCount):
                subLogger = logger.getChild("globalCoord %d" % (i, ))
                obj = fvw(wSub, axisOrder=ao, logger=subLogger)

                if obj is None:
                    return None

                globalCoords[i] = obj

        # Get the offsets to the actual glyph variation data

        if w.length() < offsetSize * (glyphCount + 1):
            logger.error(('V1043', (),
                          "The 'gvar' glyph variation array extends past the "
                          "table boundaries."))

            return None

        offsets = w.group("HL"[flags & 1], glyphCount + 1)

        if offsetSize == 2:
            offsets = [2 * n for n in offsets]

        if list(offsets) != sorted(offsets):
            logger.error(
                ('V1044', (),
                 "The per-glyph offsets are not monotonically increasing."))

            return None

        for glyphIndex, (off1, off2) in enumerate(utilities.pairwise(offsets)):
            w.align(2)

            if off1 == off2:
                continue

            # For now I'm assuming the presence of a 'glyf' table...

            glyphObj = e.glyf[glyphIndex]

            if glyphObj.isComposite:
                pointCount = len(glyphObj.components)
            else:
                pointCount = glyphObj.pointCount()

            wSub = w.subWalker(off1 + dataOff)
            subLogger = logger.getChild("glyph %d" % (glyphIndex, ))

            wasOK = r._fillGlyph_validated(wSub,
                                           glyphIndex,
                                           globalCoords,
                                           pointCount,
                                           logger=subLogger,
                                           **kwArgs)

            if not wasOK:
                return None

        return r
示例#17
0
    def buildBinary(self, w, **kwArgs):
        """
        Add the binary data for the Gvar object to the specified writer.
        
        Apart from the usual keyword argument ('stakeValue'), this method also
        supports the 'sharedPointsAllowed' keyword argument (default True).
        """

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

        sharingEnabled = kwArgs.pop('sharedPointsAllowed', True)
        e = kwArgs['editor']
        w.add("L", self.VERSION)
        w.add("H", len(self.axisOrder))
        dGlobalCoords = self._findGlobalCoords(**kwArgs)
        w.add("H", len(dGlobalCoords))

        if dGlobalCoords:
            globalCoordsStake = w.getNewStake()
            w.addUnresolvedOffset("L", stakeValue, globalCoordsStake)

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

        w.add("H", e.maxp.numGlyphs)
        w.add("H", 1)  # hard-code for 32-bit offsets, for now
        dataStake = w.getNewStake()
        w.addUnresolvedOffset("L", stakeValue, dataStake)
        numGlyphs = utilities.getFontGlyphCount(**kwArgs)
        glyphStakes = [w.getNewStake() for i in range(numGlyphs + 1)]

        for stake in glyphStakes:
            w.addUnresolvedOffset("L", dataStake, stake)

        # Now emit the global coordinates:

        if dGlobalCoords:
            w.stakeCurrentWithValue(globalCoordsStake)
            it = sorted(iter(dGlobalCoords.items()),
                        key=operator.itemgetter(1))

            for coord, globalIndex in it:
                coord.buildBinary(w, axisOrder=self.axisOrder, **kwArgs)

        # Now emit the actual variation data

        w.stakeCurrentWithValue(dataStake)
        gd = self.glyphData
        glyfTable = e.glyf
        hmtxTable = e.hmtx

        for glyphIndex in range(numGlyphs):
            w.alignToByteMultiple(2)
            w.stakeCurrentWithValue(glyphStakes[glyphIndex])

            if glyphIndex not in gd:
                continue

            glyphObj = glyfTable[glyphIndex]

            # We only skip this glyph if it has neither outline nor advance.
            # Zero-advance accents can still vary, as can the width of a space
            # glyph, so both criteria must be met to be skipped.

            if not (glyphObj or hmtxTable[glyphIndex].advance):
                continue

            if glyphObj.isComposite:
                pointCount = len(glyphObj.components) + 4
            else:
                pointCount = glyphObj.pointCount(editor=e) + 4

            pd = gd[glyphIndex]

            if sharingEnabled:
                commonPoints = pd.findCommonPoints()
            else:
                commonPoints = None

            invDict = pd.makeInvertDict()
            tupleCount = len(invDict)

            if commonPoints is not None:
                tupleCount |= 0x8000

            w.add("H", tupleCount)
            sortedCoords = sorted(invDict)
            tupleDataStake = w.getNewStake()
            w.addUnresolvedOffset("H", glyphStakes[glyphIndex], tupleDataStake)
            tupleSizeStakes = []
            privatePoints = []  # bools

            for coord in sortedCoords:
                dPointToDelta = invDict[coord]
                tupleSizeStakes.append(w.addDeferredValue("H"))
                mask = 0

                if coord not in dGlobalCoords:
                    mask |= 0x8000
                else:
                    mask = dGlobalCoords[coord]

                if any(obj.effectiveDomain for obj in dPointToDelta.values()):
                    mask |= 0x4000

                if commonPoints is not None and frozenset(
                        dPointToDelta) == commonPoints:
                    #if commonPoints is not None and set(dPointToDelta) in commonPoints:
                    privatePoints.append(False)

                else:
                    mask |= 0x2000
                    privatePoints.append(True)

                w.add("H", mask)

                if mask & 0x8000:
                    coord.buildBinary(w)

                if mask & 0x4000:
                    # we assume there's one unique domain; check this?
                    domPts = [
                        p for p, delta in dPointToDelta.items()
                        if delta.effectiveDomain
                    ]

                    dom = dPointToDelta[domPts[0]]
                    dom.effectiveDomain.edge1.buildBinary(w)
                    dom.effectiveDomain.edge2.buildBinary(w)

            # The array header is now done, so put out the shared points (if
            # any), and then stake the start of the actual tuple data.

            w.stakeCurrentWithValue(tupleDataStake)

            if commonPoints is not None:
                packedCommons = packed_points.PackedPoints(commonPoints)
                packedCommons.buildBinary(w, pointCount=pointCount - 4)

            # Now that the header is done, emit the actual points/deltas

            for t in zip(sortedCoords, tupleSizeStakes, privatePoints):
                coord, stake, isPrivate = t
                wSub = writer.LinkedWriter()
                dPointToDelta = invDict[coord]
                sortedPoints = sorted(dPointToDelta)

                if isPrivate:
                    packed_points.PackedPoints(dPointToDelta).buildBinary(
                        wSub, pointCount=pointCount - 4)

                # Emit x-deltas, in order, to wSub

                vx = [dPointToDelta[i].x for i in sortedPoints]
                self._emitDeltaBand(wSub, vx)

                # Emit y-deltas, in order, to wSub

                vy = [dPointToDelta[i].y for i in sortedPoints]
                self._emitDeltaBand(wSub, vy)

                bs = wSub.binaryString()
                w.setDeferredValue(stake, "H", len(bs))
                w.addString(bs)

        w.alignToByteMultiple(2)
        w.stakeCurrentWithValue(glyphStakes[-1])