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