286 lines
8.3 KiB
Python
286 lines
8.3 KiB
Python
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 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 = len(self.kernTable)
|
|
searchRange, entrySelector, rangeShift = getSearchRange(nPairs, 6)
|
|
searchRange &= 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}
|