[AAT] Decode the morx
ligature actions table
This commit is contained in:
parent
a7ddb0b609
commit
d373808341
@ -8,7 +8,7 @@ from fontTools.ttLib import getSearchRange
|
||||
from .otBase import (CountReference, FormatSwitchingBaseTable,
|
||||
OTTableWriter, ValueRecordFactory)
|
||||
from .otTables import (AATStateTable, AATState, AATAction,
|
||||
ContextualMorphAction)
|
||||
ContextualMorphAction, LigatureMorphAction)
|
||||
from functools import partial
|
||||
import struct
|
||||
import logging
|
||||
@ -901,6 +901,7 @@ class STXHeader(BaseConverter):
|
||||
classTableReader = reader.getSubReader(0)
|
||||
stateArrayReader = reader.getSubReader(0)
|
||||
entryTableReader = reader.getSubReader(0)
|
||||
ligActionReader = None
|
||||
table.GlyphClassCount = reader.readULong()
|
||||
classTableReader.seek(pos + reader.readULong())
|
||||
stateArrayReader.seek(pos + reader.readULong())
|
||||
@ -908,6 +909,9 @@ class STXHeader(BaseConverter):
|
||||
if self.perGlyphLookup is not None:
|
||||
perGlyphTableReader = reader.getSubReader(0)
|
||||
perGlyphTableReader.seek(pos + reader.readULong())
|
||||
if issubclass(self.tableClass, LigatureMorphAction):
|
||||
ligActionReader = reader.getSubReader(0)
|
||||
ligActionReader.seek(pos + reader.readULong())
|
||||
table.GlyphClasses = self.classLookup.read(classTableReader,
|
||||
font, tableDict)
|
||||
numStates = int((entryTableReader.pos - stateArrayReader.pos)
|
||||
@ -919,17 +923,18 @@ class STXHeader(BaseConverter):
|
||||
entryIndex = stateArrayReader.readUShort()
|
||||
state.Transitions[glyphClass] = \
|
||||
self._readTransition(entryTableReader,
|
||||
entryIndex, font)
|
||||
entryIndex, font,
|
||||
ligActionReader)
|
||||
if self.perGlyphLookup is not None:
|
||||
table.PerGlyphLookups = self._readPerGlyphLookups(
|
||||
table, perGlyphTableReader, font)
|
||||
return table
|
||||
|
||||
def _readTransition(self, reader, entryIndex, font):
|
||||
def _readTransition(self, reader, entryIndex, font, ligActionReader):
|
||||
transition = self.tableClass()
|
||||
entryReader = reader.getSubReader(
|
||||
reader.pos + entryIndex * transition.staticSize)
|
||||
transition.decompile(entryReader, font)
|
||||
transition.decompile(entryReader, font, ligActionReader)
|
||||
return transition
|
||||
|
||||
def _countPerGlyphLookups(self, table):
|
||||
|
@ -87,7 +87,8 @@ class RearrangementMorphAction(AATAction):
|
||||
if self.MarkLast: flags |= 0x2000
|
||||
writer.writeUShort(flags)
|
||||
|
||||
def decompile(self, reader, font):
|
||||
def decompile(self, reader, font, ligActionReader):
|
||||
assert ligActionReader is None
|
||||
self.NewState = reader.readUShort()
|
||||
flags = reader.readUShort()
|
||||
self.Verb = flags & 0xF
|
||||
@ -145,7 +146,8 @@ class ContextualMorphAction(AATAction):
|
||||
writer.writeUShort(self.MarkIndex)
|
||||
writer.writeUShort(self.CurrentIndex)
|
||||
|
||||
def decompile(self, reader, font):
|
||||
def decompile(self, reader, font, ligActionReader):
|
||||
assert ligActionReader is None
|
||||
self.NewState = reader.readUShort()
|
||||
flags = reader.readUShort()
|
||||
self.SetMark = bool(flags & 0x8000)
|
||||
@ -187,30 +189,67 @@ class ContextualMorphAction(AATAction):
|
||||
self.CurrentIndex = safeEval(eltAttrs["value"])
|
||||
|
||||
|
||||
class LigAction(object):
|
||||
def __init__(self):
|
||||
self.Store = False
|
||||
# GlyphIndexDelta is a (possibly negative) delta that gets
|
||||
# added to the glyph ID at the top of the AAT runtime
|
||||
# execution stack. It is *not* a byte offset into the
|
||||
# morx table. The result of the addition, which is performed
|
||||
# at run time by the shaping engine, is an index into
|
||||
# the ligature components table. See 'morx' specification.
|
||||
# In the AAT specification, this field is called Offset;
|
||||
# but its meaning is quite different from other offsets
|
||||
# in either AAT or OpenType, so we use a different name.
|
||||
self.GlyphIndexDelta = 0
|
||||
|
||||
|
||||
class LigatureMorphAction(AATAction):
|
||||
staticSize = 6
|
||||
_FLAGS = ["SetComponent", "DontAdvance", "PerformAction"]
|
||||
_FLAGS = ["SetComponent", "DontAdvance"]
|
||||
|
||||
def __init__(self):
|
||||
self.NewState = 0
|
||||
self.SetComponent, self.DontAdvance = False, False
|
||||
self.PerformAction = False
|
||||
self.ReservedFlags = 0
|
||||
self.LigActionIndex = 0
|
||||
self.Actions = []
|
||||
|
||||
def decompile(self, reader, font):
|
||||
def decompile(self, reader, font, ligActionReader):
|
||||
assert ligActionReader is not None
|
||||
self.NewState = reader.readUShort()
|
||||
flags = reader.readUShort()
|
||||
self.SetComponent = bool(flags & 0x8000)
|
||||
self.DontAdvance = bool(flags & 0x4000)
|
||||
self.PerformAction = bool(flags & 0x2000)
|
||||
performAction = bool(flags & 0x2000)
|
||||
# As of 2017-09-12, the 'morx' specification says that
|
||||
# the reserved bitmask in ligature subtables is 0x3FFF.
|
||||
# However, the specification also defines a flag 0x2000,
|
||||
# so the reserved value should actually be 0x1FFF.
|
||||
# TODO: Report this specification bug to Apple.
|
||||
self.ReservedFlags = flags & 0x1FFF
|
||||
self.LigActionIndex = reader.readUShort()
|
||||
ligActionIndex = reader.readUShort()
|
||||
if performAction:
|
||||
self.Actions = self._decompileLigActions(
|
||||
ligActionReader, ligActionIndex)
|
||||
else:
|
||||
self.Actions = []
|
||||
|
||||
def _decompileLigActions(self, ligActionReader, ligActionIndex):
|
||||
actions = []
|
||||
last = False
|
||||
reader = ligActionReader.getSubReader(
|
||||
ligActionReader.pos + ligActionIndex * 4)
|
||||
while not last:
|
||||
value = reader.readULong()
|
||||
last = bool(value & 0x80000000)
|
||||
action = LigAction()
|
||||
actions.append(action)
|
||||
action.Store = bool(value & 0x40000000)
|
||||
delta = value & 0x3FFFFFFF
|
||||
if delta >= 0x20000000: # sign-extend 30-bit value
|
||||
delta = -0x40000000 + delta
|
||||
action.GlyphIndexDelta = delta
|
||||
return actions
|
||||
|
||||
def toXML(self, xmlWriter, font, attrs, name):
|
||||
xmlWriter.begintag(name, **attrs)
|
||||
@ -218,9 +257,11 @@ class LigatureMorphAction(AATAction):
|
||||
xmlWriter.simpletag("NewState", value=self.NewState)
|
||||
xmlWriter.newline()
|
||||
self._writeFlagsToXML(xmlWriter)
|
||||
if self.PerformAction:
|
||||
xmlWriter.simpletag("LigActionIndex",
|
||||
value=self.LigActionIndex)
|
||||
for action in self.Actions:
|
||||
attribs = [("GlyphIndexDelta", action.GlyphIndexDelta)]
|
||||
if action.Store:
|
||||
attribs.append(("Flags", "Store"))
|
||||
xmlWriter.simpletag("Action", attribs)
|
||||
xmlWriter.newline()
|
||||
xmlWriter.endtag(name)
|
||||
xmlWriter.newline()
|
||||
|
@ -528,12 +528,12 @@ MORX_LIGATURE_DATA = deHexStr(
|
||||
'0003 8000 ' # 168: Entries[2].NewState=3, .Flags=0x8000 (SetComponent)
|
||||
'0000 ' # 172: Entries[2].ActionIndex=<n/a> because no 0x2000 flag
|
||||
'0000 A000 ' # 174: Entries[3].NewState=0, .Flags=0xA000 (SetComponent,Act)
|
||||
'0000 ' # 178: Entries[3].ActionIndex=0 (start at LigAction[0])
|
||||
'0000 ' # 178: Entries[3].ActionIndex=0 (start at Action[0])
|
||||
|
||||
# Ligature actions table.
|
||||
'3FFF FFE7 ' # 180: Action 0, part 1
|
||||
'3FFF FFED ' # 184: Action 0, part 2
|
||||
'BFFF FFF2 ' # 188: Action 0, part 3
|
||||
'3FFF FFE7 ' # 180: Action[0].Flags=0, .GlyphIndexDelta=-25
|
||||
'3FFF FFED ' # 184: Action[1].Flags=0, .GlyphIndexDelta=-19
|
||||
'BFFF FFF2 ' # 188: Action[2].Flags=<end of list>, .GlyphIndexDelta=-14
|
||||
|
||||
# Ligature component table.
|
||||
'0000 0001 ' # 192: LigComponent[0]=0, LigComponent[1]=1
|
||||
@ -674,8 +674,10 @@ MORX_LIGATURE_XML = [
|
||||
' </Transition>',
|
||||
' <Transition onGlyphClass="6">',
|
||||
' <NewState value="0"/>',
|
||||
' <Flags value="SetComponent,PerformAction"/>',
|
||||
' <LigActionIndex value="0"/>',
|
||||
' <Flags value="SetComponent"/>',
|
||||
' <Action GlyphIndexDelta="-25"/>',
|
||||
' <Action GlyphIndexDelta="-19"/>',
|
||||
' <Action GlyphIndexDelta="-14"/>',
|
||||
' </Transition>',
|
||||
' </State>',
|
||||
' </StateTable>',
|
||||
|
@ -385,7 +385,8 @@ class RearrangementMorphActionTest(unittest.TestCase):
|
||||
|
||||
def testDecompileToXML(self):
|
||||
r = otTables.RearrangementMorphAction()
|
||||
r.decompile(OTTableReader(deHexStr("1234fffd")), self.font)
|
||||
r.decompile(OTTableReader(deHexStr("1234fffd")),
|
||||
self.font, ligActionReader=None)
|
||||
toXML = lambda w, f: r.toXML(w, f, {"Test": "Foo"}, "Transition")
|
||||
self.assertEqual(getXML(toXML, self.font), [
|
||||
'<Transition Test="Foo">',
|
||||
@ -412,7 +413,8 @@ class ContextualMorphActionTest(unittest.TestCase):
|
||||
|
||||
def testDecompileToXML(self):
|
||||
a = otTables.ContextualMorphAction()
|
||||
a.decompile(OTTableReader(deHexStr("1234f117deadbeef")), self.font)
|
||||
a.decompile(OTTableReader(deHexStr("1234f117deadbeef")),
|
||||
self.font, ligActionReader=None)
|
||||
toXML = lambda w, f: a.toXML(w, f, {"Test": "Foo"}, "Transition")
|
||||
self.assertEqual(getXML(toXML, self.font), [
|
||||
'<Transition Test="Foo">',
|
||||
@ -425,6 +427,27 @@ class ContextualMorphActionTest(unittest.TestCase):
|
||||
])
|
||||
|
||||
|
||||
class LigatureMorphActionTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.font = FakeFont(['.notdef', 'A', 'B', 'C'])
|
||||
|
||||
def testDecompileToXML(self):
|
||||
a = otTables.LigatureMorphAction()
|
||||
ligActionReader = OTTableReader(deHexStr("DEADBEEF 7FFFFFFE 80000003"))
|
||||
a.decompile(OTTableReader(deHexStr("1234FAB30001")),
|
||||
self.font, ligActionReader)
|
||||
toXML = lambda w, f: a.toXML(w, f, {"Test": "Foo"}, "Transition")
|
||||
self.assertEqual(getXML(toXML, self.font), [
|
||||
'<Transition Test="Foo">',
|
||||
' <NewState value="4660"/>', # 0x1234 = 4660
|
||||
' <Flags value="SetComponent,DontAdvance"/>',
|
||||
' <ReservedFlags value="0x1AB3"/>',
|
||||
' <Action GlyphIndexDelta="-2" Flags="Store"/>',
|
||||
' <Action GlyphIndexDelta="3"/>',
|
||||
'</Transition>',
|
||||
])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
sys.exit(unittest.main())
|
||||
|
Loading…
x
Reference in New Issue
Block a user