123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- 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 = min(len(self.kernTable), 0xFFFF)
- searchRange, entrySelector, rangeShift = getSearchRange(nPairs, 6)
- searchRange &= 0xFFFF
- entrySelector = min(entrySelector, 0xFFFF)
- rangeShift = min(rangeShift, 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}
|