blob: 6e21a4bd289a63c5720e3ef82c7879a64b66ae2d [file] [log] [blame]
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 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:]
nPairs = min(nPairs, len(data) // 6)
datas = array.array("H", data[:6 * nPairs])
if sys.byteorder != "big": # pragma: no cover
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)
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
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}