[_k_e_r_n] fix compilation of AAT kern tables

- When compiling kern subtables for version=1.0 kern tables (AAT)
  the subtable header was written incorrectly: there is no version,
  the length is a uint32 and there's an additional uint16 for
  tupleIndex
- Use the 'coverage' low byte to select subtable "format", instead
  of the 'version' field, only present in OT kern subtable header.
  The getkern method was failing with AttributeError on 'unknown'
  subtable formats, as their class only has 'format' instead of
  'version' attribute. The 'version' attribute is renamed to
  'format' also to avoid confusion, but the old one is kept for
  backward compatiblity. In the only implemeted subtable class,
  'format' becomes a class attribute rather than instance's
  (it must always be 0).
- KernTable_format_0 now takes an 'apple=False' argument, used to
  know the different headers and whether to read/write tupleIndex.
- minor pep8 whitespace and indentation fixes
- A new 'tupleIndex' attribute is written out to TTX for apple
  kern subtables. Old ttx files which lack that attribute will
  still be read (with a warning) and will default to tupleIndex=0
  when recompiled or dumped with current fonttools.

Fixes #1089
This commit is contained in:
Cosimo Lupo 2017-11-03 16:11:36 +00:00
parent 5f2177a990
commit 69d3bfadcc

View File

@ -17,7 +17,7 @@ class table__k_e_r_n(DefaultTable.DefaultTable):
def getkern(self, format):
for subtable in self.kernTables:
if subtable.version == format:
if subtable.format == format:
return subtable
return None # not found
@ -33,21 +33,23 @@ class table__k_e_r_n(DefaultTable.DefaultTable):
else:
self.version = version
data = data[4:]
tablesIndex = []
self.kernTables = []
for i in range(nTables):
if self.version == 1.0:
# Apple
length, coverage, tupleIndex = struct.unpack(">lHH", data[:8])
version = coverage & 0xff
length, coverage = struct.unpack(">LH", data[:6])
else:
version, length = struct.unpack(">HH", data[:4])
length = int(length)
if version not in kern_classes:
subtable = KernTable_format_unkown(version)
# in OpenType spec the "version" field refers to the common
# subtable header; the actual subtable format is stored in
# the last 8 mask bits of "coverage" field.
# Since this "version" is always 0 (and is not present in the
# later AAT extensions), we simply ignore it here
_, length, coverage = struct.unpack(">HHH", data[:6])
subtableFormat = coverage & 0xff
if subtableFormat not in kern_classes:
subtable = KernTable_format_unkown(subtableFormat)
else:
subtable = kern_classes[version]()
subtable.apple = apple
subtable = kern_classes[subtableFormat](apple)
subtable.decompile(data[:length], ttFont)
self.kernTables.append(subtable)
data = data[length:]
@ -59,7 +61,7 @@ class table__k_e_r_n(DefaultTable.DefaultTable):
nTables = 0
if self.version == 1.0:
# AAT Apple's "new" format.
data = struct.pack(">ll", fl2fi(self.version, 16), nTables)
data = struct.pack(">LL", fl2fi(self.version, 16), nTables)
else:
data = struct.pack(">HH", self.version, nTables)
if hasattr(self, "kernTables"):
@ -85,26 +87,41 @@ class table__k_e_r_n(DefaultTable.DefaultTable):
if format not in kern_classes:
subtable = KernTable_format_unkown(format)
else:
subtable = kern_classes[format]()
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):
version, length, coverage = (0,0,0)
if not self.apple:
version, length, coverage = struct.unpack(">HHH", 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:
version, length, coverage = struct.unpack(">LHH", data[:8])
length, coverage, tupleIndex = struct.unpack(">LHH", data[:8])
data = data[8:]
self.version, self.coverage = int(version), int(coverage)
assert self.format == coverage & 0xFF, "unsupported format"
self.coverage = coverage
self.tupleIndex = tupleIndex
self.kernTable = kernTable = {}
nPairs, searchRange, entrySelector, rangeShift = struct.unpack(">HHHH", data[:8])
nPairs, searchRange, entrySelector, rangeShift = struct.unpack(
">HHHH", data[:8])
data = data[8:]
nPairs = min(nPairs, len(data) // 6)
@ -115,50 +132,95 @@ class KernTable_format_0(object):
glyphOrder = ttFont.getGlyphOrder()
for k in range(nPairs):
left, right, value = next(it), next(it), next(it)
if value >= 32768: value -= 65536
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)
# 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)
data = struct.pack(">HHHH", nPairs, searchRange, entrySelector, rangeShift)
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())
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())
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)
return struct.pack(">HHH", self.version, len(data) + 6, self.coverage) + data
# ensure mask bits 8-15 (subtable format) are set to 0
self.coverage &= ~0xFF
if not self.apple:
version = 0
length = len(data) + 6
header = struct.pack(">HHH", version, length, 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(">LHH", length, self.coverage, self.tupleIndex)
return header + data
def toXML(self, writer, ttFont):
writer.begintag("kernsubtable", coverage=self.coverage, format=0)
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)
])
("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"])
self.version = safeEval(attrs["format"])
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: