[morx] Use a sub-reader when decoding AAT ligatures

Since the AAT ligature subtable does not encode the number of ligature
glyphs, we need to infer this from the total structure length. We pass
this around by creating a custom sub-reader that only has the substruct
as its data. There might have been easier ways to accomplish this, but
we should anyway change the XML output for MorxSubtables to use custom
flag names, similar to what we're already doing for flags of morph actions.
Having a custom converter for MorxSubtables is in preparation for that
later XML format change.
This commit is contained in:
Sascha Brawer 2017-10-24 21:07:30 +02:00
parent 1ac4a020ec
commit 5316bb83b6

View File

@ -6,9 +6,10 @@ from fontTools.misc.fixedTools import (
from fontTools.misc.textTools import pad, safeEval
from fontTools.ttLib import getSearchRange
from .otBase import (CountReference, FormatSwitchingBaseTable,
OTTableWriter, ValueRecordFactory)
from .otTables import (AATStateTable, AATState, AATAction,
ContextualMorphAction, LigatureMorphAction)
OTTableReader, OTTableWriter, ValueRecordFactory)
from .otTables import (lookupTypes, AATStateTable, AATState, AATAction,
ContextualMorphAction, LigatureMorphAction,
MorxSubtable)
from functools import partial
import struct
import logging
@ -51,8 +52,7 @@ def buildConverters(tableSpec, tableNamespace):
converterClass = Struct
else:
converterClass = eval(tp, tableNamespace, converterMapping)
if tp in ('MortChain', 'MortSubtable',
'MorxChain', 'MorxSubtable'):
if tp in ('MortChain', 'MortSubtable', 'MorxChain'):
tableClass = tableNamespace.get(tp)
else:
tableClass = tableNamespace.get(tableName)
@ -882,6 +882,83 @@ class AATLookupWithDataOffset(BaseConverter):
lookup.xmlWrite(xmlWriter, font, value, name, attrs)
class MorxSubtableConverter(BaseConverter):
def __init__(self, name, repeat, aux):
BaseConverter.__init__(self, name, repeat, aux)
def read(self, reader, font, tableDict):
pos = reader.pos
m = MorxSubtable()
m.StructLength = reader.readULong()
m.CoverageFlags = reader.readUInt8()
m.Reserved = reader.readUShort()
m.MorphType = reader.readUInt8()
m.SubFeatureFlags = reader.readULong()
tableClass = lookupTypes["morx"].get(m.MorphType)
if tableClass is None:
assert False, ("unsupported 'morx' lookup type %s" %
morphType)
# To decode AAT ligatures, we need to know the subtable size.
# The easiest way to pass this along is to create a new reader
# that works on just the subtable as its data.
headerLength = reader.pos - pos
subReader = OTTableReader(
data=reader.data[reader.pos : reader.pos + m.StructLength - headerLength],
tableTag=reader.tableTag)
m.SubStruct = tableClass()
m.SubStruct.decompile(subReader, font)
reader.seek(pos + m.StructLength)
return m
def xmlWrite(self, xmlWriter, font, value, name, attrs):
xmlWriter.begintag(name, attrs)
xmlWriter.newline()
xmlWriter.comment("StructLength=%d" % value.StructLength)
xmlWriter.newline()
# TODO: Emit flags in meaningful form, similar to what we
# already do for the individual morph types.
xmlWriter.simpletag("CoverageFlags",
value="%d" % value.CoverageFlags)
xmlWriter.newline()
xmlWriter.simpletag("Reserved",
value="%d" % value.Reserved)
xmlWriter.newline()
xmlWriter.comment("MorphType=%d" % value.MorphType)
xmlWriter.newline()
xmlWriter.simpletag("SubFeatureFlags",
value="0x%08x" % value.SubFeatureFlags)
xmlWriter.newline()
value.SubStruct.toXML(xmlWriter, font)
xmlWriter.endtag(name)
xmlWriter.newline()
def xmlRead(self, attrs, content, font):
m = MorxSubtable()
for eltName, eltAttrs, eltContent in filter(istuple, content):
# TODO: Parse meaningful flags, similar to what we
# already do for the individual morph types.
if eltName == "CoverageFlags":
m.CoverageFlags = safeEval(eltAttrs["value"])
elif eltName == "Reserved":
m.Reserved = safeEval(eltAttrs["value"])
elif eltName == "SubFeatureFlags":
m.SubFeatureFlags = safeEval(eltAttrs["value"])
elif eltName.endswith("Morph"):
m.fromXML(eltName, eltAttrs, eltContent, font)
else:
assert False, eltName
return m
def write(self, writer, font, tableDict, value, repeatIndex=None):
lengthIndex = len(writer.items)
before = writer.getDataLength()
value.StructLength = 0xdeadbeef
value.compile(writer, font)
assert writer.items[lengthIndex] == b"\xde\xad\xbe\xef"
length = writer.getDataLength() - before
writer.items[lengthIndex] = struct.pack(">L", length)
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html#ExtendedStateHeader
# TODO: Untangle the implementation of the various lookup-specific formats.
class STXHeader(BaseConverter):
@ -949,8 +1026,6 @@ class STXHeader(BaseConverter):
return transition
def _readLigatures(self, reader, font):
# TODO: Take limit from StructLength of enclosing MorxSubtable.
# The following only works for the very last subtable in morx.
limit = len(reader.data)
numLigatureGlyphs = (limit - reader.pos) // 2
return [font.getGlyphName(g)
@ -1480,7 +1555,7 @@ converterMapping = {
"MortChain": StructWithLength,
"MortSubtable": StructWithLength,
"MorxChain": StructWithLength,
"MorxSubtable": StructWithLength,
"MorxSubtable": MorxSubtableConverter,
# "Template" types
"AATLookup": lambda C: partial(AATLookup, tableClass=C),