Ejemplo n.º 1
0
    def _makeLookup2(self, keySpan):
        """
        Creates and returns a bytestring for the lookup-specific portions of
        the output data, for lookup format 2.
        
        >>> s1 = splits_distance.Splits_Distance([10, 500, 800])
        >>> s2 = splits_distance.Splits_Distance([250, 500])
        >>> s3 = s1.__deepcopy__()
        >>> d = Lcar({5: s1, 6: s1, 15: s2, 25: s3})
        >>> ks = span.Span(d)
        >>> utilities.hexdump(d._makeLookup2(ks))
               0 | 0002 0006 0003 000C  0001 0006 0006 0005 |................|
              10 | 002A 000F 000F 003A  0019 0019 0032 FFFF |.*.....:.....2..|
              20 | FFFF 0000 0003 000A  01F4 0320 0003 000A |........... ....|
              30 | 01F4 0320 0002 00FA  01F4                |... ......      |
        """

        w = writer.LinkedWriter()
        stakeStart = w.stakeCurrent()
        w.add("H", 2)
        bStake = w.getNewStake()
        w.addReplaceableString(bStake, b' ' * 10)  # don't know nUnits yet...
        pool = {}  # an id-pool; safe because scope is limited to this method
        nUnits = 0

        for spanStart, spanEnd in keySpan:
            firstGlyph = spanStart

            it = ((id(self[i]), self[i])
                  for i in range(spanStart, spanEnd + 1))

            for k, g in itertools.groupby(it, operator.itemgetter(0)):
                v = list(g)
                if k not in pool:
                    pool[k] = (w.getNewStake(), v[0][1])

                n = len(v)
                w.add("2H", firstGlyph + n - 1, firstGlyph)

                w.addUnresolvedOffset("H",
                                      stakeStart,
                                      pool[k][0],
                                      offsetByteDelta=6)

                firstGlyph += n
                nUnits += 1

        # add the sentinel (doesn't count toward nUnits)
        w.add("3H", 0xFFFF, 0xFFFF, 0)

        # we now know nUnits, so retrofit the BSH
        w.setReplaceableString(
            bStake,
            bsh.BSH(nUnits=nUnits, unitSize=6).binaryString())

        # Now add the deferred values
        for stake, obj in sorted(pool.values(), key=operator.itemgetter(1)):
            obj.buildBinary(w, stakeValue=stake)

        return w.binaryString()
Ejemplo n.º 2
0
    def _makeLookup6(self, keySpan):
        w = writer.LinkedWriter()
        stakeStart = w.stakeCurrent()
        w.add("H", 6)
        bsh.BSH(nUnits=len(self), unitSize=4).buildBinary(w)
        pool = {}  # an id-pool

        for spanStart, spanEnd in keySpan:  # a convenient already-sorted source...
            for glyphIndex in range(spanStart, spanEnd + 1):
                w.add("H", glyphIndex)
                obj = self[glyphIndex]
                thisID = id(obj)

                if thisID not in pool:
                    pool[thisID] = (w.getNewStake(), obj)

                w.addUnresolvedOffset("H",
                                      stakeStart,
                                      pool[thisID][0],
                                      offsetByteDelta=6)

        # add the sentinel (doesn't count toward nUnits)
        w.add("2H", 0xFFFF, 0)

        # Now add the deferred values
        for stake, obj in sorted(pool.values(), key=operator.itemgetter(1)):
            obj.buildBinary(w, stakeValue=stake)

        return w.binaryString()
Ejemplo n.º 3
0
    def _buildBinary_sub(self, contigRanges):
        """
        Does the actual work for binary string building. This method returns
        None if so many idRangeOffset values get accumulated that the offsets
        go over 64K; in this case the caller will redo the contigRanges via
        subdivision, which will always succeed.
        """

        w = writer.LinkedWriter()
        startLength = w.byteLength
        w.add("H", 4)  # format
        lengthStake = w.addDeferredValue("H")
        w.add("H", (self.language or 0))
        segCount = len(contigRanges) + 1
        idDeltas = [0] * len(contigRanges)
        idRangeOffsets = [0] * len(contigRanges)
        currentGIA = []
        f = self._fmt4Analyze

        for i, x in enumerate(contigRanges):
            glyphs = [self.get(k, 0) for k in range(x[0], x[1] + 1)]
            idDelta = f(glyphs, x[0])
            idDeltas[i] = idDelta % 65536

            if idDelta == 0:
                ro = 2 * (len(currentGIA) + segCount - i)

                if ro >= 0x10000:
                    return None

                idRangeOffsets[i] = ro
                currentGIA.extend(glyphs)

            else:
                idRangeOffsets[i] = 0

        idDeltas.append(1)
        idRangeOffsets.append(0)
        w.add("H", 2 * segCount)
        bshObj = bsh.BSH(nUnits=segCount, unitSize=2)
        w.addString(bshObj.binaryString(skipUnitSize=True)[2:])
        w.addGroup("H", (x[1] for x in contigRanges))
        w.add("hH", -1, 0)
        w.addGroup("H", (x[0] for x in contigRanges))
        w.add("h", -1)
        w.addGroup("H", idDeltas)
        w.addGroup("H", idRangeOffsets)

        if currentGIA:
            w.addGroup("H", currentGIA)

        # We pin the length field to FFFF in case it's larger.
        byteSize = min(0xFFFF, w.byteLength - startLength)
        w.setDeferredValue(lengthStake, "H", byteSize)
        return w.binaryString()
Ejemplo n.º 4
0
    def _makeLookup4(self, keySpan):
        """
        Creates and returns a bytestring for the lookup-specific portions of
        the output data, for lookup format 4.
        
        >>> s1 = splits_distance.Splits_Distance([10, 500, 800])
        >>> s2 = splits_distance.Splits_Distance([250, 500])
        >>> s3 = s1.__deepcopy__()
        >>> d = Lcar({5: s1, 6: s1, 15: s2, 25: s3})
        >>> ks = span.Span(d)
        >>> utilities.hexdump(d._makeLookup4(ks))
               0 | 0004 0006 0003 000C  0001 0006 0006 0005 |................|
              10 | 002A 000F 000F 002E  0019 0019 0030 FFFF |.*...........0..|
              20 | FFFF 0000 0032 0032  0042 003A 0003 000A |.....2.2.B.:....|
              30 | 01F4 0320 0003 000A  01F4 0320 0002 00FA |... ....... ....|
              40 | 01F4                                     |..              |
        """

        w = writer.LinkedWriter()
        stakeStart = w.stakeCurrent()
        w.add("H", 4)
        bsh.BSH(nUnits=len(keySpan), unitSize=6).buildBinary(w)
        pool = {}  # an id-pool
        offsetOffsets = [w.getNewStake() for obj in keySpan]

        for (spanStart, spanEnd), stake in zip(keySpan, offsetOffsets):
            w.add("2H", spanEnd, spanStart)
            w.addUnresolvedOffset("H", stakeStart, stake, offsetByteDelta=6)

        # add the sentinel (doesn't count toward nUnits)
        w.add("3H", 0xFFFF, 0xFFFF, 0)

        for (spanStart, spanEnd), stake in zip(keySpan, offsetOffsets):
            w.stakeCurrentWithValue(stake)

            for glyphIndex in range(spanStart, spanEnd + 1):
                obj = self[glyphIndex]
                thisID = id(obj)

                if thisID not in pool:
                    pool[thisID] = (w.getNewStake(), obj)

                w.addUnresolvedOffset("H",
                                      stakeStart,
                                      pool[thisID][0],
                                      offsetByteDelta=6)

        # Now add the deferred values
        for stake, obj in sorted(pool.values(), key=operator.itemgetter(1)):
            obj.buildBinary(w, stakeValue=stake)

        return w.binaryString()
Ejemplo n.º 5
0
    def _validate_binSearch(w, logger):
        bad = (False, None)

        if w.length() < 2:
            logger.error(('V0017', (), "Insufficient bytes for segCount."))
            return bad

        segCountTimesTwo = w.unpack("H")

        if segCountTimesTwo % 2:
            logger.error(('E0312', (), "Segment count times two is odd."))
            return bad

        segCount = segCountTimesTwo // 2

        if segCount:
            logger.info(('V0018', (segCount, ), "There are %d segments."))
        else:
            logger.warning(('V0945', (), "The format 4 segment count is 0."))

        if w.length() < 6:
            logger.error(
                ('V0019', (), "Insufficient bytes for binary search fields."))

            return bad

        searchRange, entrySelector, rangeShift = w.unpack("3H")
        bshAvatar = bsh.BSH(nUnits=segCount, unitSize=2)

        if searchRange != bshAvatar.searchRange:
            logger.warning(('E0311', (searchRange, bshAvatar.searchRange),
                            "Incorrect searchRange 0x%04X; should be 0x%04X."))

        else:
            logger.info(('V0020', (), "searchRange is correct."))

        if entrySelector != bshAvatar.entrySelector:
            logger.warning(
                ('E0305', (entrySelector, bshAvatar.entrySelector),
                 "Incorrect entrySelector 0x%04X; should be 0x%04X."))

        else:
            logger.info(('V0021', (), "entrySelector is correct."))

        if rangeShift != bshAvatar.rangeShift:
            logger.warning(('E0309', (rangeShift, bshAvatar.rangeShift),
                            "Incorrect rangeShift 0x%04X; should be 0x%04X."))

        else:
            logger.info(('V0022', (), "rangeShift is correct."))

        return True, segCount
Ejemplo n.º 6
0
    def _makeLookup6(self, keySpan):
        """
        Creates and returns a bytestring for the lookup-specific portions of
        the output data, for lookup format 6.
        
        >>> s1 = splits_distance.Splits_Distance([10, 500, 800])
        >>> s2 = splits_distance.Splits_Distance([250, 500])
        >>> s3 = s1.__deepcopy__()
        >>> d = Lcar({5: s1, 6: s1, 15: s2, 25: s3})
        >>> ks = span.Span(d)
        >>> utilities.hexdump(d._makeLookup6(ks))
               0 | 0006 0004 0004 0010  0002 0000 0005 0026 |...............&|
              10 | 0006 0026 000F 0036  0019 002E FFFF 0000 |...&...6........|
              20 | 0003 000A 01F4 0320  0003 000A 01F4 0320 |....... ....... |
              30 | 0002 00FA 01F4                           |......          |
        """

        w = writer.LinkedWriter()
        stakeStart = w.stakeCurrent()
        w.add("H", 6)
        bsh.BSH(nUnits=len(self), unitSize=4).buildBinary(w)
        pool = {}  # an id-pool

        for spanStart, spanEnd in keySpan:  # a nice already-sorted source...
            for glyphIndex in range(spanStart, spanEnd + 1):
                w.add("H", glyphIndex)
                obj = self[glyphIndex]
                thisID = id(obj)

                if thisID not in pool:
                    pool[thisID] = (w.getNewStake(), obj)

                w.addUnresolvedOffset("H",
                                      stakeStart,
                                      pool[thisID][0],
                                      offsetByteDelta=6)

        # add the sentinel (doesn't count toward nUnits)
        w.add("2H", 0xFFFF, 0)

        # Now add the deferred values
        for stake, obj in sorted(pool.values(), key=operator.itemgetter(1)):
            obj.buildBinary(w, stakeValue=stake)

        return w.binaryString()
Ejemplo n.º 7
0
    def _makeLookup2(self, keySpan):
        w = writer.LinkedWriter()
        stakeStart = w.stakeCurrent()
        w.add("H", 2)
        bStake = w.getNewStake()
        w.addReplaceableString(bStake, ' ' * 10)  # we don't know nUnits yet...
        pool = {}  # an id-pool; safe because scope is limited to this method
        nUnits = 0

        for spanStart, spanEnd in keySpan:
            firstGlyph = spanStart
            it = ((id(self[i]), self[i])
                  for i in range(spanStart, spanEnd + 1))

            for k, g in itertools.groupby(it, operator.itemgetter(0)):
                v = list(g)
                if k not in pool:
                    pool[k] = (w.getNewStake(), v[0][1])

                n = len(v)
                w.add("2H", firstGlyph + n - 1, firstGlyph)
                w.addUnresolvedOffset(
                    "H", stakeStart, pool[k][0],
                    offsetByteDelta=6)  # table offset, not lookup offset
                firstGlyph += n
                nUnits += 1

        # add the sentinel (doesn't count toward nUnits)
        w.add("3H", 0xFFFF, 0xFFFF, 0)

        # we now know nUnits, so retrofit the BSH
        w.setReplaceableString(
            bStake,
            bsh.BSH(nUnits=nUnits, unitSize=6).binaryString())

        # Now add the deferred values
        for stake, obj in sorted(pool.values(), key=operator.itemgetter(1)):
            obj.buildBinary(w, stakeValue=stake)

        return w.binaryString()
Ejemplo n.º 8
0
    def _makeLookup4(self, keySpan):
        w = writer.LinkedWriter()
        stakeStart = w.stakeCurrent()  # start of lookup
        w.add("H", 4)
        bsh.BSH(nUnits=len(keySpan), unitSize=6).buildBinary(w)
        pool = {}  # id(obj) -> (objStake, obj)
        ooStakes = [w.getNewStake() for obj in keySpan]

        for (spanStart, spanEnd), stake in zip(keySpan, ooStakes):
            w.add("2H", spanEnd, spanStart)
            w.addUnresolvedOffset("H", stakeStart,
                                  stake)  # from lookup, not table!!

        # add the sentinel
        w.add("3H", 0xFFFF, 0xFFFF, 0)

        # add the offset vectors
        for (spanStart, spanEnd), stake in zip(keySpan, ooStakes):
            w.stakeCurrentWithValue(stake)

            for glyphIndex in range(spanStart, spanEnd + 1):
                obj = self[glyphIndex]
                thisID = id(obj)

                if thisID not in pool:
                    pool[thisID] = (w.getNewStake(), obj)

                w.addUnresolvedOffset(
                    "H", stakeStart, pool[thisID][0],
                    offsetByteDelta=6)  # from table, not lookup!!

        # add the actual objects
        for stake, obj in sorted(pool.values(), key=operator.itemgetter(1)):
            obj.buildBinary(w, stakeValue=stake)

        return w.binaryString()
Ejemplo n.º 9
0
    def fromvalidatedwalker(cls, w, **kwArgs):
        """
        Creates and returns a Format0 object from the specified walker, which
        should start at the subtable header. This method does extensive
        validation via the logging module (the client should have done a
        logging.basicConfig call prior to calling this method, unless a logger
        is passed in via the 'logger' keyword argument).
        
        >>> logger = utilities.makeDoctestLogger('test')
        >>> s = _testingValues[1].binaryString()
        >>> fvb = Format0.fromvalidatedbytes
        >>> obj = fvb(s[0:3], logger=logger)
        test.format0 - DEBUG - Walker has 3 remaining bytes.
        test.format0 - ERROR - Insufficient bytes.

        >>> s = _testingValues[2].binaryString()
        >>> obj = fvb(s, logger=logger)
        test.format0 - DEBUG - Walker has 20 remaining bytes.
        test.format0 - INFO - There are 2 kerning pairs.
        test.format0.[0].glyphpair - DEBUG - Walker has 12 remaining bytes.
        test.format0.[1].glyphpair - DEBUG - Walker has 6 remaining bytes.
        test.format0 - WARNING - Value for pair (14, 96) is 0
        
        >>> s = utilities.fromhex(
        ...   "00 03 00 0C 00 01 00 06 00 05 00 10 FF E7 00 05 "
        ...   "00 10 FF E7 00 05 00 10 FF E9") 
        >>> obj = fvb(s, logger=logger)
        test.format0 - DEBUG - Walker has 26 remaining bytes.
        test.format0 - INFO - There are 3 kerning pairs.
        test.format0.[0].glyphpair - DEBUG - Walker has 18 remaining bytes.
        test.format0.[1].glyphpair - DEBUG - Walker has 12 remaining bytes.
        test.format0 - WARNING - Duplicate entry for pair (5, 16) with value -25
        test.format0.[2].glyphpair - DEBUG - Walker has 6 remaining bytes.
        test.format0 - ERROR - Duplicate entry for pair (5, 16) with different value -23 (-25)
        """

        kwArgs.pop('fontGlyphCount', None)
        logger = kwArgs.pop('logger', logging.getLogger())
        logger = logger.getChild("format0")
        byteLength = w.length()

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

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

        nPairs, searchRange, entrySelector, rangeShift = w.unpack("4H")
        bTest = bsh.BSH(nUnits=nPairs, unitSize=6)
        """
        The parent 'kern' table stores a 'length' record for each
        subtable. In 'kern' table version 0, this field is defined as a
        USHORT and will thus overflow when nPairs is > 10920 (14 bytes
        of header + (6 * 10920) = 65534). Although most systems can deal
        with the font correctly (provided that the actual byte length
        corresponds with nPairs), we still detect and warn about the
        condition as some applications do not correctly deal with the
        mismatch.
        """
        if nPairs > 10920:
            logger.warning(
                ('V0920', (), "Subtable declares more than 10920 pairs."))

        srmod = bTest.searchRange % 65536
        if searchRange != srmod:
            logger.error(
                ('E1602', (searchRange, srmod),
                 "Subtable searchRange %d is incorrect; should be %d."))

            return None

        esmod = bTest.entrySelector % 65536
        if entrySelector != esmod:
            logger.error(
                ('E1600', (entrySelector, esmod),
                 "Subtable entrySelector %d is incorrect; should be %d."))

            return None

        rsmod = bTest.rangeShift % 65536
        if rangeShift != rsmod:
            logger.error(
                ('E1601', (rangeShift, rsmod),
                 "Subtable rangeShift %d is incorrect; should be %d."))

            return None

        if w.length() < 6 * nPairs:
            logger.error(('V0136', (), "Insufficient bytes for pair data."))
            return None

        logger.info(('V0137', (nPairs, ), "There are %d kerning pairs."))

        r = cls({}, **utilities.filterKWArgs(cls, kwArgs))
        okToReturn = True
        GP = glyphpair.GlyphPair.fromvalidatedwalker
        sortCheck = [None] * nPairs

        for i in range(nPairs):
            key = GP(w, logger=logger.getChild("[%d]" % (i, )), **kwArgs)

            if key is None:
                return None

            sortCheck[i] = key
            value = w.unpack("h")

            if key in r:
                if value == r[key]:
                    logger.warning(
                        ('V0139', (key, value),
                         "Duplicate entry for pair %s with value %d"))

                else:
                    logger.error(('V0140', (key, value, r[key]),
                                  "Duplicate entry for pair %s with different "
                                  "value %d (%d)"))

                    okToReturn = False

            if value == 0:
                logger.warning(('V0141', (key, ), "Value for pair %s is 0"))

            else:
                r[key] = value

        if sortCheck != sorted(sortCheck):
            logger.error(('V0138', (), "Kerning pairs are not sorted."))
            okToReturn = False

        # Skip the sentinel, if one is present (spec is ambiguous)

        if w.length() >= 6:  # last subtable might just zero-pad, so check this
            sentinel = w.unpack("3h", advance=False)

            if sentinel == (-1, -1, 0):
                w.skip(6)

            # check for non-zero pad? i.e. sentinel[2] != 0?

        return (r if okToReturn else None)