[_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:
parent
5f2177a990
commit
69d3bfadcc
@ -17,7 +17,7 @@ class table__k_e_r_n(DefaultTable.DefaultTable):
|
|||||||
|
|
||||||
def getkern(self, format):
|
def getkern(self, format):
|
||||||
for subtable in self.kernTables:
|
for subtable in self.kernTables:
|
||||||
if subtable.version == format:
|
if subtable.format == format:
|
||||||
return subtable
|
return subtable
|
||||||
return None # not found
|
return None # not found
|
||||||
|
|
||||||
@ -33,21 +33,23 @@ class table__k_e_r_n(DefaultTable.DefaultTable):
|
|||||||
else:
|
else:
|
||||||
self.version = version
|
self.version = version
|
||||||
data = data[4:]
|
data = data[4:]
|
||||||
tablesIndex = []
|
|
||||||
self.kernTables = []
|
self.kernTables = []
|
||||||
for i in range(nTables):
|
for i in range(nTables):
|
||||||
if self.version == 1.0:
|
if self.version == 1.0:
|
||||||
# Apple
|
# Apple
|
||||||
length, coverage, tupleIndex = struct.unpack(">lHH", data[:8])
|
length, coverage = struct.unpack(">LH", data[:6])
|
||||||
version = coverage & 0xff
|
|
||||||
else:
|
else:
|
||||||
version, length = struct.unpack(">HH", data[:4])
|
# in OpenType spec the "version" field refers to the common
|
||||||
length = int(length)
|
# subtable header; the actual subtable format is stored in
|
||||||
if version not in kern_classes:
|
# the last 8 mask bits of "coverage" field.
|
||||||
subtable = KernTable_format_unkown(version)
|
# 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:
|
else:
|
||||||
subtable = kern_classes[version]()
|
subtable = kern_classes[subtableFormat](apple)
|
||||||
subtable.apple = apple
|
|
||||||
subtable.decompile(data[:length], ttFont)
|
subtable.decompile(data[:length], ttFont)
|
||||||
self.kernTables.append(subtable)
|
self.kernTables.append(subtable)
|
||||||
data = data[length:]
|
data = data[length:]
|
||||||
@ -59,7 +61,7 @@ class table__k_e_r_n(DefaultTable.DefaultTable):
|
|||||||
nTables = 0
|
nTables = 0
|
||||||
if self.version == 1.0:
|
if self.version == 1.0:
|
||||||
# AAT Apple's "new" format.
|
# AAT Apple's "new" format.
|
||||||
data = struct.pack(">ll", fl2fi(self.version, 16), nTables)
|
data = struct.pack(">LL", fl2fi(self.version, 16), nTables)
|
||||||
else:
|
else:
|
||||||
data = struct.pack(">HH", self.version, nTables)
|
data = struct.pack(">HH", self.version, nTables)
|
||||||
if hasattr(self, "kernTables"):
|
if hasattr(self, "kernTables"):
|
||||||
@ -85,26 +87,41 @@ class table__k_e_r_n(DefaultTable.DefaultTable):
|
|||||||
if format not in kern_classes:
|
if format not in kern_classes:
|
||||||
subtable = KernTable_format_unkown(format)
|
subtable = KernTable_format_unkown(format)
|
||||||
else:
|
else:
|
||||||
subtable = kern_classes[format]()
|
apple = self.version == 1.0
|
||||||
|
subtable = kern_classes[format](apple)
|
||||||
self.kernTables.append(subtable)
|
self.kernTables.append(subtable)
|
||||||
subtable.fromXML(name, attrs, content, ttFont)
|
subtable.fromXML(name, attrs, content, ttFont)
|
||||||
|
|
||||||
|
|
||||||
class KernTable_format_0(object):
|
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):
|
def decompile(self, data, ttFont):
|
||||||
version, length, coverage = (0,0,0)
|
|
||||||
if not self.apple:
|
if not self.apple:
|
||||||
version, length, coverage = struct.unpack(">HHH", data[:6])
|
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:]
|
data = data[6:]
|
||||||
else:
|
else:
|
||||||
version, length, coverage = struct.unpack(">LHH", data[:8])
|
length, coverage, tupleIndex = struct.unpack(">LHH", data[:8])
|
||||||
data = 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 = {}
|
self.kernTable = kernTable = {}
|
||||||
|
|
||||||
nPairs, searchRange, entrySelector, rangeShift = struct.unpack(">HHHH", data[:8])
|
nPairs, searchRange, entrySelector, rangeShift = struct.unpack(
|
||||||
|
">HHHH", data[:8])
|
||||||
data = data[8:]
|
data = data[8:]
|
||||||
|
|
||||||
nPairs = min(nPairs, len(data) // 6)
|
nPairs = min(nPairs, len(data) // 6)
|
||||||
@ -115,35 +132,69 @@ class KernTable_format_0(object):
|
|||||||
glyphOrder = ttFont.getGlyphOrder()
|
glyphOrder = ttFont.getGlyphOrder()
|
||||||
for k in range(nPairs):
|
for k in range(nPairs):
|
||||||
left, right, value = next(it), next(it), next(it)
|
left, right, value = next(it), next(it), next(it)
|
||||||
if value >= 32768: value -= 65536
|
if value >= 32768:
|
||||||
|
value -= 65536
|
||||||
try:
|
try:
|
||||||
kernTable[(glyphOrder[left], glyphOrder[right])] = value
|
kernTable[(glyphOrder[left], glyphOrder[right])] = value
|
||||||
except IndexError:
|
except IndexError:
|
||||||
# Slower, but will not throw an IndexError on an invalid glyph id.
|
# Slower, but will not throw an IndexError on an invalid
|
||||||
kernTable[(ttFont.getGlyphName(left), ttFont.getGlyphName(right))] = value
|
# glyph id.
|
||||||
|
kernTable[(
|
||||||
|
ttFont.getGlyphName(left),
|
||||||
|
ttFont.getGlyphName(right))] = value
|
||||||
if len(data) > 6 * nPairs + 4: # Ignore up to 4 bytes excess
|
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)
|
log.warning(
|
||||||
|
"excess data in 'kern' subtable: %d bytes",
|
||||||
|
len(data) - 6 * nPairs)
|
||||||
|
|
||||||
def compile(self, ttFont):
|
def compile(self, ttFont):
|
||||||
nPairs = len(self.kernTable)
|
nPairs = len(self.kernTable)
|
||||||
searchRange, entrySelector, rangeShift = getSearchRange(nPairs, 6)
|
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)
|
# yeehee! (I mean, turn names into indices)
|
||||||
try:
|
try:
|
||||||
reverseOrder = ttFont.getReverseGlyphMap()
|
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:
|
except KeyError:
|
||||||
# Slower, but will not throw KeyError on invalid glyph id.
|
# Slower, but will not throw KeyError on invalid glyph id.
|
||||||
getGlyphID = ttFont.getGlyphID
|
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:
|
for left, right, value in kernTable:
|
||||||
data = data + struct.pack(">HHh", left, right, value)
|
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):
|
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()
|
writer.newline()
|
||||||
items = sorted(self.kernTable.items())
|
items = sorted(self.kernTable.items())
|
||||||
for (left, right), value in items:
|
for (left, right), value in items:
|
||||||
@ -158,7 +209,18 @@ class KernTable_format_0(object):
|
|||||||
|
|
||||||
def fromXML(self, name, attrs, content, ttFont):
|
def fromXML(self, name, attrs, content, ttFont):
|
||||||
self.coverage = safeEval(attrs["coverage"])
|
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"):
|
if not hasattr(self, "kernTable"):
|
||||||
self.kernTable = {}
|
self.kernTable = {}
|
||||||
for element in content:
|
for element in content:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user