[_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):
|
||||
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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user