from __future__ import print_function, division, absolute_import from fontTools.misc.py23 import * from fontTools.ttLib import getSearchRange from fontTools.misc.textTools import safeEval, readHex from fontTools.misc.fixedTools import ( fixedToFloat as fi2fl, floatToFixed as fl2fi) from . import DefaultTable import struct import sys import array import logging log = logging.getLogger(__name__) class table__k_e_r_n(DefaultTable.DefaultTable): def getkern(self, format): for subtable in self.kernTables: if subtable.format == format: return subtable return None # not found def decompile(self, data, ttFont): version, nTables = struct.unpack(">HH", data[:4]) apple = False if (len(data) >= 8) and (version == 1): # AAT Apple's "new" format. Hm. version, nTables = struct.unpack(">LL", data[:8]) self.version = fi2fl(version, 16) data = data[8:] apple = True else: self.version = version data = data[4:] self.kernTables = [] for i in range(nTables): if self.version == 1.0: # Apple length, coverage, subtableFormat = struct.unpack( ">LBB", data[:6]) else: # in OpenType spec the "version" field refers to the common # subtable header; the actual subtable format is stored in # the 8-15 mask bits of "coverage" field. # This "version" is always 0 so we ignore it here _, length, subtableFormat, coverage = struct.unpack( ">HHBB", data[:6]) if nTables == 1 and subtableFormat == 0: # The "length" value is ignored since some fonts # (like OpenSans and Calibri) have a subtable larger than # its value. nPairs, = struct.unpack(">H", data[6:8]) calculated_length = (nPairs * 6) + 14 if length != calculated_length: log.warning( "'kern' subtable longer than defined: " "%d bytes instead of %d bytes" % (calculated_length, length) ) length = calculated_length if subtableFormat not in kern_classes: subtable = KernTable_format_unkown(subtableFormat) else: subtable = kern_classes[subtableFormat](apple) subtable.decompile(data[:length], ttFont) self.kernTables.append(subtable) data = data[length:] def compile(self, ttFont): if hasattr(self, "kernTables"): nTables = len(self.kernTables) else: nTables = 0 if self.version == 1.0: # AAT Apple's "new" format. data = struct.pack(">LL", fl2fi(self.version, 16), nTables) else: data = struct.pack(">HH", self.version, nTables) if hasattr(self, "kernTables"): for subtable in self.kernTables: data = data + subtable.compile(ttFont) return data def toXML(self, writer, ttFont): writer.simpletag("version", value=self.version) writer.newline() for subtable in self.kernTables: subtable.toXML(writer, ttFont) def fromXML(self, name, attrs, content, ttFont): if name == "version": self.version = safeEval(attrs["value"]) return if name != "kernsubtable": return if not hasattr(self, "kernTables"): self.kernTables = [] format = safeEval(attrs["format"]) if format not in kern_classes: subtable = KernTable_format_unkown(format) else: apple = self.version == 1.0 subtable = kern_classes[format](apple) self.kernTables.append(subtable) subtable.fromXML(name, attrs, content, ttFont) class KernTable_format_0(object): # 'version' is kept for backward compatibility version = format = 0 def __init__(self, apple=False): self.apple = apple def decompile(self, data, ttFont): if not self.apple: version, length, subtableFormat, coverage = struct.unpack( ">HHBB", data[:6]) if version != 0: from fontTools.ttLib import TTLibError raise TTLibError( "unsupported kern subtable version: %d" % version) tupleIndex = None # Should we also assert length == len(data)? data = data[6:] else: length, coverage, subtableFormat, tupleIndex = struct.unpack( ">LBBH", data[:8]) data = data[8:] assert self.format == subtableFormat, "unsupported format" self.coverage = coverage self.tupleIndex = tupleIndex self.kernTable = kernTable = {} nPairs, searchRange, entrySelector, rangeShift = struct.unpack( ">HHHH", data[:8]) data = data[8:] datas = array.array("H", data[:6 * nPairs]) if sys.byteorder != "big": datas.byteswap() it = iter(datas) glyphOrder = ttFont.getGlyphOrder() for k in range(nPairs): left, right, value = next(it), next(it), next(it) if value >= 32768: value -= 65536 try: kernTable[(glyphOrder[left], glyphOrder[right])] = value except IndexError: # Slower, but will not throw an IndexError on an invalid # glyph id. kernTable[( ttFont.getGlyphName(left), ttFont.getGlyphName(right))] = value if len(data) > 6 * nPairs + 4: # Ignore up to 4 bytes excess log.warning( "excess data in 'kern' subtable: %d bytes", len(data) - 6 * nPairs) def compile(self, ttFont): nPairs = len(self.kernTable) searchRange, entrySelector, rangeShift = getSearchRange(nPairs, 6) searchRange &= 0xFFFF data = struct.pack( ">HHHH", nPairs, searchRange, entrySelector, rangeShift) # yeehee! (I mean, turn names into indices) try: reverseOrder = ttFont.getReverseGlyphMap() kernTable = sorted( (reverseOrder[left], reverseOrder[right], value) for ((left, right), value) in self.kernTable.items()) except KeyError: # Slower, but will not throw KeyError on invalid glyph id. getGlyphID = ttFont.getGlyphID kernTable = sorted( (getGlyphID(left), getGlyphID(right), value) for ((left, right), value) in self.kernTable.items()) for left, right, value in kernTable: data = data + struct.pack(">HHh", left, right, value) if not self.apple: version = 0 length = len(data) + 6 if length >= 0x10000: log.warning('"kern" subtable overflow, ' 'truncating length value while preserving pairs.') length &= 0xFFFF header = struct.pack( ">HHBB", version, length, self.format, self.coverage) else: if self.tupleIndex is None: # sensible default when compiling a TTX from an old fonttools # or when inserting a Windows-style format 0 subtable into an # Apple version=1.0 kern table log.warning("'tupleIndex' is None; default to 0") self.tupleIndex = 0 length = len(data) + 8 header = struct.pack( ">LBBH", length, self.coverage, self.format, self.tupleIndex) return header + data def toXML(self, writer, ttFont): attrs = dict(coverage=self.coverage, format=self.format) if self.apple: if self.tupleIndex is None: log.warning("'tupleIndex' is None; default to 0") attrs["tupleIndex"] = 0 else: attrs["tupleIndex"] = self.tupleIndex writer.begintag("kernsubtable", **attrs) writer.newline() items = sorted(self.kernTable.items()) for (left, right), value in items: writer.simpletag("pair", [ ("l", left), ("r", right), ("v", value) ]) writer.newline() writer.endtag("kernsubtable") writer.newline() def fromXML(self, name, attrs, content, ttFont): self.coverage = safeEval(attrs["coverage"]) subtableFormat = safeEval(attrs["format"]) if self.apple: if "tupleIndex" in attrs: self.tupleIndex = safeEval(attrs["tupleIndex"]) else: # previous fontTools versions didn't export tupleIndex log.warning( "Apple kern subtable is missing 'tupleIndex' attribute") self.tupleIndex = None else: self.tupleIndex = None assert subtableFormat == self.format, "unsupported format" if not hasattr(self, "kernTable"): self.kernTable = {} for element in content: if not isinstance(element, tuple): continue name, attrs, content = element self.kernTable[(attrs["l"], attrs["r"])] = safeEval(attrs["v"]) def __getitem__(self, pair): return self.kernTable[pair] def __setitem__(self, pair, value): self.kernTable[pair] = value def __delitem__(self, pair): del self.kernTable[pair] class KernTable_format_unkown(object): def __init__(self, format): self.format = format def decompile(self, data, ttFont): self.data = data def compile(self, ttFont): return self.data def toXML(self, writer, ttFont): writer.begintag("kernsubtable", format=self.format) writer.newline() writer.comment("unknown 'kern' subtable format") writer.newline() writer.dumphex(self.data) writer.endtag("kernsubtable") writer.newline() def fromXML(self, name, attrs, content, ttFont): self.decompile(readHex(content), ttFont) kern_classes = {0: KernTable_format_0}