[AAT] Support morx
tables with Rearrangement subtables
This commit is contained in:
parent
49fc88244b
commit
31b02d0bed
@ -3,15 +3,17 @@ from fontTools.misc.py23 import *
|
||||
from fontTools.misc.fixedTools import (
|
||||
fixedToFloat as fi2fl, floatToFixed as fl2fi, ensureVersionIsLong as fi2ve,
|
||||
versionToFixed as ve2fi)
|
||||
from fontTools.misc.textTools import safeEval
|
||||
from fontTools.misc.textTools import pad, safeEval
|
||||
from fontTools.ttLib import getSearchRange
|
||||
from .otBase import ValueRecordFactory, CountReference, OTTableWriter
|
||||
from .otTables import AATStateTable, AATState
|
||||
from functools import partial
|
||||
import struct
|
||||
import logging
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
istuple = lambda t: isinstance(t, tuple)
|
||||
|
||||
|
||||
def buildConverters(tableSpec, tableNamespace):
|
||||
@ -874,6 +876,128 @@ class AATLookupWithDataOffset(BaseConverter):
|
||||
lookup.xmlWrite(xmlWriter, font, value, name, attrs)
|
||||
|
||||
|
||||
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html#ExtendedStateHeader
|
||||
class STXHeader(BaseConverter):
|
||||
def __init__(self, name, repeat, aux, tableClass):
|
||||
BaseConverter.__init__(self, name, repeat, aux, tableClass)
|
||||
assert tableClass is not None
|
||||
self.classLookup = AATLookup("GlyphClasses", None, None, UShort)
|
||||
|
||||
def read(self, reader, font, tableDict):
|
||||
table = AATStateTable()
|
||||
pos = reader.pos
|
||||
classTableReader = reader.getSubReader(0)
|
||||
stateArrayReader = reader.getSubReader(0)
|
||||
entryTableReader = reader.getSubReader(0)
|
||||
table.GlyphClassCount = reader.readULong()
|
||||
classTableReader.seek(pos + reader.readULong())
|
||||
stateArrayReader.seek(pos + reader.readULong())
|
||||
entryTableReader.seek(pos + reader.readULong())
|
||||
table.GlyphClasses = self.classLookup.read(classTableReader,
|
||||
font, tableDict)
|
||||
numStates = int((entryTableReader.pos - stateArrayReader.pos)
|
||||
/ (table.GlyphClassCount * 2))
|
||||
for stateIndex in range(numStates):
|
||||
state = AATState()
|
||||
table.States.append(state)
|
||||
for glyphClass in range(table.GlyphClassCount):
|
||||
entryIndex = stateArrayReader.readUShort()
|
||||
state.Transitions[glyphClass] = \
|
||||
self._readTransition(entryTableReader,
|
||||
entryIndex, font)
|
||||
return table
|
||||
|
||||
def _readTransition(self, reader, entryIndex, font):
|
||||
transition = self.tableClass()
|
||||
entryReader = reader.getSubReader(
|
||||
reader.pos + entryIndex * transition.staticSize)
|
||||
transition.decompile(entryReader, font)
|
||||
return transition
|
||||
|
||||
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
||||
glyphClassWriter = OTTableWriter()
|
||||
self.classLookup.write(glyphClassWriter, font, tableDict,
|
||||
value.GlyphClasses, repeatIndex=None)
|
||||
glyphClassData = pad(glyphClassWriter.getAllData(), 4)
|
||||
glyphClassCount = max(value.GlyphClasses.values()) + 1
|
||||
glyphClassTableOffset = 16 # size of STXHeader
|
||||
stateArrayWriter = OTTableWriter()
|
||||
entries, entryIDs = [], {}
|
||||
for state in value.States:
|
||||
for glyphClass in range(glyphClassCount):
|
||||
transition = state.Transitions[glyphClass]
|
||||
entryWriter = OTTableWriter()
|
||||
transition.compile(entryWriter, font)
|
||||
entryData = entryWriter.getAllData()
|
||||
assert len(entryData) == transition.staticSize, ( \
|
||||
"%s has staticSize %d, "
|
||||
"but actually wrote %d bytes" % (
|
||||
repr(transition),
|
||||
transition.staticSize,
|
||||
len(entryData)))
|
||||
entryIndex = entryIDs.get(entryData)
|
||||
if entryIndex is None:
|
||||
entryIndex = len(entries)
|
||||
entryIDs[entryData] = entryIndex
|
||||
entries.append(entryData)
|
||||
stateArrayWriter.writeUShort(entryIndex)
|
||||
stateArrayOffset = glyphClassTableOffset + len(glyphClassData)
|
||||
stateArrayData = stateArrayWriter.getAllData()
|
||||
entryTableOffset = stateArrayOffset + len(stateArrayData)
|
||||
writer.writeULong(glyphClassCount)
|
||||
writer.writeULong(glyphClassTableOffset)
|
||||
writer.writeULong(stateArrayOffset)
|
||||
writer.writeULong(entryTableOffset)
|
||||
writer.writeData(glyphClassData)
|
||||
writer.writeData(stateArrayData)
|
||||
for entry in entries:
|
||||
writer.writeData(entry)
|
||||
|
||||
def xmlWrite(self, xmlWriter, font, value, name, attrs):
|
||||
xmlWriter.begintag(name, attrs)
|
||||
xmlWriter.newline()
|
||||
xmlWriter.comment("GlyphClassCount=%s" %value.GlyphClassCount)
|
||||
xmlWriter.newline()
|
||||
for g, klass in sorted(value.GlyphClasses.items()):
|
||||
xmlWriter.simpletag("GlyphClass", glyph=g, value=klass)
|
||||
xmlWriter.newline()
|
||||
for stateIndex, state in enumerate(value.States):
|
||||
xmlWriter.begintag("State", index=stateIndex)
|
||||
xmlWriter.newline()
|
||||
for glyphClass, trans in sorted(state.Transitions.items()):
|
||||
trans.toXML(xmlWriter, font=font,
|
||||
attrs={"onGlyphClass": glyphClass},
|
||||
name="Transition")
|
||||
xmlWriter.endtag("State")
|
||||
xmlWriter.newline()
|
||||
xmlWriter.endtag(name)
|
||||
xmlWriter.newline()
|
||||
|
||||
def xmlRead(self, attrs, content, font):
|
||||
table = AATStateTable()
|
||||
for eltName, eltAttrs, eltContent in filter(istuple, content):
|
||||
if eltName == "GlyphClass":
|
||||
glyph = eltAttrs["glyph"]
|
||||
value = eltAttrs["value"]
|
||||
table.GlyphClasses[glyph] = safeEval(value)
|
||||
elif eltName == "State":
|
||||
state = self._xmlReadState(eltAttrs, eltContent, font)
|
||||
table.States.append(state)
|
||||
table.GlyphClassCount = max(table.GlyphClasses.values()) + 1
|
||||
return table
|
||||
|
||||
def _xmlReadState(self, attrs, content, font):
|
||||
state = AATState()
|
||||
for eltName, eltAttrs, eltContent in filter(istuple, content):
|
||||
if eltName == "Transition":
|
||||
glyphClass = safeEval(eltAttrs["onGlyphClass"])
|
||||
transition = self.tableClass()
|
||||
transition.fromXML(eltName, eltAttrs,
|
||||
eltContent, font)
|
||||
state.Transitions[glyphClass] = transition
|
||||
return state
|
||||
|
||||
|
||||
class DeltaValue(BaseConverter):
|
||||
|
||||
def read(self, reader, font, tableDict):
|
||||
@ -1048,6 +1172,7 @@ converterMapping = {
|
||||
# "Template" types
|
||||
"AATLookup": lambda C: partial(AATLookup, tableClass=C),
|
||||
"AATLookupWithDataOffset": lambda C: partial(AATLookupWithDataOffset, tableClass=C),
|
||||
"STXHeader": lambda C: partial(STXHeader, tableClass=C),
|
||||
"OffsetTo": lambda C: partial(Table, tableClass=C),
|
||||
"LOffsetTo": lambda C: partial(LTable, tableClass=C),
|
||||
}
|
||||
|
@ -1387,7 +1387,7 @@ otData = [
|
||||
]),
|
||||
|
||||
('RearrangementMorph', [
|
||||
('struct', 'StateHeader', None, None, 'Header.'),
|
||||
('STXHeader(AATRearrangement)', 'StateTable', None, None, 'Finite-state transducer table.'),
|
||||
]),
|
||||
|
||||
('ContextualMorph', [
|
||||
|
@ -1,10 +1,11 @@
|
||||
# coding: utf-8
|
||||
"""fontTools.ttLib.tables.otTables -- A collection of classes representing the various
|
||||
OpenType subtables.
|
||||
|
||||
Most are constructed upon import from data in otData.py, all are populated with
|
||||
converter objects from otConverters.py.
|
||||
"""
|
||||
from __future__ import print_function, division, absolute_import
|
||||
from __future__ import print_function, division, absolute_import, unicode_literals
|
||||
from fontTools.misc.py23 import *
|
||||
from fontTools.misc.textTools import safeEval
|
||||
from .otBase import BaseTable, FormatSwitchingBaseTable
|
||||
@ -15,6 +16,107 @@ import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AATStateTable(object):
|
||||
def __init__(self):
|
||||
self.GlyphClasses = {} # GlyphName --> GlyphClass
|
||||
self.States = [] # List of AATState, indexed by state number
|
||||
|
||||
class AATState(object):
|
||||
def __init__(self):
|
||||
self.Transitions = {} # GlyphClass --> {AATRearrangement, ...}
|
||||
|
||||
|
||||
class AATRearrangement(object):
|
||||
staticSize = 4
|
||||
|
||||
_VERBS = {
|
||||
0: "no change",
|
||||
1: "Ax ⇒ xA",
|
||||
2: "xD ⇒ Dx",
|
||||
3: "AxD ⇒ DxA",
|
||||
4: "ABx ⇒ xAB",
|
||||
5: "ABx ⇒ xBA",
|
||||
6: "xCD ⇒ CDx",
|
||||
7: "xCD ⇒ DCx",
|
||||
8: "AxCD ⇒ CDxA",
|
||||
9: "AxCD ⇒ DCxA",
|
||||
10: "ABxD ⇒ DxAB",
|
||||
11: "ABxD ⇒ DxBA",
|
||||
12: "ABxCD ⇒ CDxAB",
|
||||
13: "ABxCD ⇒ CDxBA",
|
||||
14: "ABxCD ⇒ DCxAB",
|
||||
15: "ABxCD ⇒ DCxBA",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.NewState = 0
|
||||
self.Verb = 0
|
||||
self.MarkFirst = False
|
||||
self.DontAdvance = False
|
||||
self.MarkLast = False
|
||||
self.ReservedFlags = 0
|
||||
|
||||
def compile(self, writer, font):
|
||||
writer.writeUShort(self.NewState)
|
||||
assert self.Verb >= 0 and self.Verb <= 15, self.Verb
|
||||
flags = self.Verb | self.ReservedFlags
|
||||
if self.MarkFirst: flags |= 0x8000
|
||||
if self.DontAdvance: flags |= 0x4000
|
||||
if self.MarkLast: flags |= 0x2000
|
||||
writer.writeUShort(flags)
|
||||
|
||||
def decompile(self, reader, font):
|
||||
self.NewState = reader.readUShort()
|
||||
flags = reader.readUShort()
|
||||
self.Verb = flags & 0xF
|
||||
self.MarkFirst = bool(flags & 0x8000)
|
||||
self.DontAdvance = bool(flags & 0x4000)
|
||||
self.MarkLast = bool(flags & 0x2000)
|
||||
self.ReservedFlags = flags & 0x1FF0
|
||||
|
||||
def toXML(self, xmlWriter, font, attrs, name):
|
||||
xmlWriter.begintag(name, **attrs)
|
||||
xmlWriter.newline()
|
||||
xmlWriter.simpletag("NewState", value=self.NewState)
|
||||
xmlWriter.newline()
|
||||
flags = [f for f in ("MarkFirst", "DontAdvance", "MarkLast")
|
||||
if self.__dict__[f]]
|
||||
if flags:
|
||||
xmlWriter.simpletag("Flags", value=",".join(flags))
|
||||
xmlWriter.newline()
|
||||
if self.ReservedFlags != 0:
|
||||
xmlWriter.simpletag("ReservedFlags",
|
||||
value='0x%04X' % self.ReservedFlags)
|
||||
xmlWriter.newline()
|
||||
xmlWriter.simpletag("Verb", value=self.Verb)
|
||||
verbComment = self._VERBS.get(self.Verb)
|
||||
if verbComment is not None:
|
||||
xmlWriter.comment(verbComment)
|
||||
xmlWriter.newline()
|
||||
xmlWriter.endtag(name)
|
||||
xmlWriter.newline()
|
||||
|
||||
def fromXML(self, name, attrs, content, font):
|
||||
self.NewState = self.Verb = self.ReservedFlags = 0
|
||||
self.MarkFirst = self.DontAdvance = self.MarkLast = False
|
||||
content = [t for t in content if isinstance(t, tuple)]
|
||||
for eltName, eltAttrs, eltContent in content:
|
||||
if eltName == "NewState":
|
||||
self.NewState = safeEval(eltAttrs["value"])
|
||||
elif eltName == "Verb":
|
||||
self.Verb = safeEval(eltAttrs["value"])
|
||||
elif eltName == "ReservedFlags":
|
||||
self.ReservedFlags = safeEval(eltAttrs["value"])
|
||||
elif eltName == "Flags":
|
||||
for flag in eltAttrs["value"].split(","):
|
||||
self._setFlag(flag.strip())
|
||||
|
||||
def _setFlag(self, flag):
|
||||
assert flag in {"MarkFirst", "DontAdvance", "MarkLast"}, \
|
||||
"unsupported flag %s" % flag
|
||||
self.__dict__[flag] = True
|
||||
|
||||
|
||||
class FeatureParams(BaseTable):
|
||||
|
||||
def compile(self, writer, font):
|
||||
@ -649,7 +751,6 @@ class LigatureSubst(FormatSwitchingBaseTable):
|
||||
ligs.append(lig)
|
||||
|
||||
|
||||
#
|
||||
# For each subtable format there is a class. However, we don't really distinguish
|
||||
# between "field name" and "format name": often these are the same. Yet there's
|
||||
# a whole bunch of fields with different names. The following dict is a mapping
|
||||
@ -995,7 +1096,7 @@ def _buildClasses():
|
||||
4: NoncontextualMorph,
|
||||
},
|
||||
'morx': {
|
||||
# 0: RearrangementMorph,
|
||||
0: RearrangementMorph,
|
||||
# 1: ContextualMorph,
|
||||
# 2: LigatureMorph,
|
||||
# 3: Reserved,
|
||||
|
@ -1,3 +1,4 @@
|
||||
# coding: utf-8
|
||||
from __future__ import print_function, division, absolute_import, unicode_literals
|
||||
from fontTools.misc.py23 import *
|
||||
from fontTools.misc.testTools import FakeFont, getXML, parseXML
|
||||
@ -85,6 +86,153 @@ MORX_NONCONTEXTUAL_XML = [
|
||||
]
|
||||
|
||||
|
||||
MORX_REARRANGEMENT_DATA = deHexStr(
|
||||
'0002 0000 ' # 0: Version=2, Reserved=0
|
||||
'0000 0001 ' # 4: MorphChainCount=1
|
||||
'0000 0001 ' # 8: DefaultFlags=1
|
||||
'0000 0078 ' # 12: StructLength=120
|
||||
'0000 0000 ' # 16: MorphFeatureCount=0
|
||||
'0000 0001 ' # 20: MorphSubtableCount=1
|
||||
'0000 0068 ' # 24: Subtable[0].StructLength=104 (+24=128)
|
||||
'80 ' # 28: Subtable[0].CoverageFlags=0x80
|
||||
'00 00 ' # 29: Subtable[0].Reserved=0
|
||||
'00 ' # 31: Subtable[0].MorphType=0/RearrangementMorph
|
||||
'0000 0001 ' # 32: Subtable[0].SubFeatureFlags=0x1
|
||||
'0000 0006 ' # 36: STXHeader.ClassCount=6
|
||||
'0000 0010 ' # 40: STXHeader.ClassTableOffset=16 (+36=52)
|
||||
'0000 0028 ' # 44: STXHeader.StateArrayOffset=40 (+36=76)
|
||||
'0000 004C ' # 48: STXHeader.EntryTableOffset=76 (+36=112)
|
||||
'0006 0004 ' # 52: ClassTable.LookupFormat=6, .UnitSize=4
|
||||
'0002 0008 ' # 56: .NUnits=2, .SearchRange=8
|
||||
'0001 0000 ' # 60: .EntrySelector=1, .RangeShift=0
|
||||
'0001 0005 ' # 64: Glyph=A; Class=5
|
||||
'0003 0004 ' # 68: Glyph=C; Class=4
|
||||
'FFFF 0000 ' # 72: Glyph=<end>; Value=0
|
||||
'0000 0001 0002 0003 0002 0001 ' # 76: State[0][0..5]
|
||||
'0003 0003 0003 0003 0003 0003 ' # 88: State[1][0..5]
|
||||
'0001 0003 0003 0003 0002 0002 ' # 100: State[2][0..5]
|
||||
'0002 FFFF ' # 112: Entries[0].NewState=2, .Flags=0xFFFF
|
||||
'0001 A00D ' # 116: Entries[1].NewState=1, .Flags=0xA00D
|
||||
'0000 8006 ' # 120: Entries[2].NewState=0, .Flags=0x8006
|
||||
'0002 0000 ' # 124: Entries[3].NewState=2, .Flags=0x0000
|
||||
) # 128: <end>
|
||||
assert len(MORX_REARRANGEMENT_DATA) == 128, len(MORX_REARRANGEMENT_DATA)
|
||||
|
||||
|
||||
MORX_REARRANGEMENT_XML = [
|
||||
'<Version value="2"/>',
|
||||
'<Reserved value="0"/>',
|
||||
'<!-- MorphChainCount=1 -->',
|
||||
'<MorphChain index="0">',
|
||||
' <DefaultFlags value="0x00000001"/>',
|
||||
' <!-- StructLength=120 -->',
|
||||
' <!-- MorphFeatureCount=0 -->',
|
||||
' <!-- MorphSubtableCount=1 -->',
|
||||
' <MorphSubtable index="0">',
|
||||
' <!-- StructLength=104 -->',
|
||||
' <CoverageFlags value="128"/>',
|
||||
' <Reserved value="0"/>',
|
||||
' <!-- MorphType=0 -->',
|
||||
' <SubFeatureFlags value="0x00000001"/>',
|
||||
' <RearrangementMorph>',
|
||||
' <StateTable>',
|
||||
' <!-- GlyphClassCount=6 -->',
|
||||
' <GlyphClass glyph="A" value="5"/>',
|
||||
' <GlyphClass glyph="C" value="4"/>',
|
||||
' <State index="0">',
|
||||
' <Transition onGlyphClass="0">',
|
||||
' <NewState value="2"/>',
|
||||
' <Flags value="MarkFirst,DontAdvance,MarkLast"/>',
|
||||
' <ReservedFlags value="0x1FF0"/>',
|
||||
' <Verb value="15"/><!-- ABxCD ⇒ DCxBA -->',
|
||||
' </Transition>',
|
||||
' <Transition onGlyphClass="1">',
|
||||
' <NewState value="1"/>',
|
||||
' <Flags value="MarkFirst,MarkLast"/>',
|
||||
' <Verb value="13"/><!-- ABxCD ⇒ CDxBA -->',
|
||||
' </Transition>',
|
||||
' <Transition onGlyphClass="2">',
|
||||
' <NewState value="0"/>',
|
||||
' <Flags value="MarkFirst"/>',
|
||||
' <Verb value="6"/><!-- xCD ⇒ CDx -->',
|
||||
' </Transition>',
|
||||
' <Transition onGlyphClass="3">',
|
||||
' <NewState value="2"/>',
|
||||
' <Verb value="0"/><!-- no change -->',
|
||||
' </Transition>',
|
||||
' <Transition onGlyphClass="4">',
|
||||
' <NewState value="0"/>',
|
||||
' <Flags value="MarkFirst"/>',
|
||||
' <Verb value="6"/><!-- xCD ⇒ CDx -->',
|
||||
' </Transition>',
|
||||
' <Transition onGlyphClass="5">',
|
||||
' <NewState value="1"/>',
|
||||
' <Flags value="MarkFirst,MarkLast"/>',
|
||||
' <Verb value="13"/><!-- ABxCD ⇒ CDxBA -->',
|
||||
' </Transition>',
|
||||
' </State>',
|
||||
' <State index="1">',
|
||||
' <Transition onGlyphClass="0">',
|
||||
' <NewState value="2"/>',
|
||||
' <Verb value="0"/><!-- no change -->',
|
||||
' </Transition>',
|
||||
' <Transition onGlyphClass="1">',
|
||||
' <NewState value="2"/>',
|
||||
' <Verb value="0"/><!-- no change -->',
|
||||
' </Transition>',
|
||||
' <Transition onGlyphClass="2">',
|
||||
' <NewState value="2"/>',
|
||||
' <Verb value="0"/><!-- no change -->',
|
||||
' </Transition>',
|
||||
' <Transition onGlyphClass="3">',
|
||||
' <NewState value="2"/>',
|
||||
' <Verb value="0"/><!-- no change -->',
|
||||
' </Transition>',
|
||||
' <Transition onGlyphClass="4">',
|
||||
' <NewState value="2"/>',
|
||||
' <Verb value="0"/><!-- no change -->',
|
||||
' </Transition>',
|
||||
' <Transition onGlyphClass="5">',
|
||||
' <NewState value="2"/>',
|
||||
' <Verb value="0"/><!-- no change -->',
|
||||
' </Transition>',
|
||||
' </State>',
|
||||
' <State index="2">',
|
||||
' <Transition onGlyphClass="0">',
|
||||
' <NewState value="1"/>',
|
||||
' <Flags value="MarkFirst,MarkLast"/>',
|
||||
' <Verb value="13"/><!-- ABxCD ⇒ CDxBA -->',
|
||||
' </Transition>',
|
||||
' <Transition onGlyphClass="1">',
|
||||
' <NewState value="2"/>',
|
||||
' <Verb value="0"/><!-- no change -->',
|
||||
' </Transition>',
|
||||
' <Transition onGlyphClass="2">',
|
||||
' <NewState value="2"/>',
|
||||
' <Verb value="0"/><!-- no change -->',
|
||||
' </Transition>',
|
||||
' <Transition onGlyphClass="3">',
|
||||
' <NewState value="2"/>',
|
||||
' <Verb value="0"/><!-- no change -->',
|
||||
' </Transition>',
|
||||
' <Transition onGlyphClass="4">',
|
||||
' <NewState value="0"/>',
|
||||
' <Flags value="MarkFirst"/>',
|
||||
' <Verb value="6"/><!-- xCD ⇒ CDx -->',
|
||||
' </Transition>',
|
||||
' <Transition onGlyphClass="5">',
|
||||
' <NewState value="0"/>',
|
||||
' <Flags value="MarkFirst"/>',
|
||||
' <Verb value="6"/><!-- xCD ⇒ CDx -->',
|
||||
' </Transition>',
|
||||
' </State>',
|
||||
' </StateTable>',
|
||||
' </RearrangementMorph>',
|
||||
' </MorphSubtable>',
|
||||
'</MorphChain>',
|
||||
]
|
||||
|
||||
|
||||
class MORXNoncontextualGlyphSubstitutionTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
@ -108,7 +256,26 @@ class MORXNoncontextualGlyphSubstitutionTest(unittest.TestCase):
|
||||
hexStr(MORX_NONCONTEXTUAL_DATA))
|
||||
|
||||
|
||||
class MORXRearrangementTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.maxDiff = None
|
||||
cls.font = FakeFont(['.nodef', 'A', 'B', 'C'])
|
||||
|
||||
def test_decompile_toXML(self):
|
||||
table = newTable('morx')
|
||||
table.decompile(MORX_REARRANGEMENT_DATA, self.font)
|
||||
self.assertEqual(getXML(table.toXML), MORX_REARRANGEMENT_XML)
|
||||
|
||||
def test_compile_fromXML(self):
|
||||
table = newTable('morx')
|
||||
for name, attrs, content in parseXML(MORX_REARRANGEMENT_XML):
|
||||
table.fromXML(name, attrs, content, font=self.font)
|
||||
self.assertEqual(hexStr(table.compile(self.font)),
|
||||
hexStr(MORX_REARRANGEMENT_DATA))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
sys.exit(unittest.main())
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
from __future__ import print_function, division, absolute_import
|
||||
# coding: utf-8
|
||||
from __future__ import print_function, division, absolute_import, unicode_literals
|
||||
from fontTools.misc.py23 import *
|
||||
from fontTools.misc.testTools import parseXML, FakeFont
|
||||
from fontTools.misc.testTools import getXML, parseXML, FakeFont
|
||||
from fontTools.misc.textTools import deHexStr, hexStr
|
||||
from fontTools.misc.xmlWriter import XMLWriter
|
||||
from fontTools.ttLib.tables.otBase import OTTableReader, OTTableWriter
|
||||
import fontTools.ttLib.tables.otTables as otTables
|
||||
import unittest
|
||||
|
||||
@ -367,6 +370,33 @@ class AlternateSubstTest(unittest.TestCase):
|
||||
})
|
||||
|
||||
|
||||
class AATRearrangementTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.font = FakeFont(['.notdef', 'A', 'B', 'C'])
|
||||
|
||||
def testCompile(self):
|
||||
r = otTables.AATRearrangement()
|
||||
r.NewState = 0x1234
|
||||
r.MarkFirst = r.DontAdvance = r.MarkLast = True
|
||||
r.ReservedFlags, r.Verb = 0x1FF0, 0xD
|
||||
writer = OTTableWriter()
|
||||
r.compile(writer, self.font)
|
||||
self.assertEqual(hexStr(writer.getAllData()), "1234fffd")
|
||||
|
||||
def testDecompileToXML(self):
|
||||
r = otTables.AATRearrangement()
|
||||
r.decompile(OTTableReader(deHexStr("1234fffd")), self.font)
|
||||
toXML = lambda w, f: r.toXML(w, f, {"Test": "Foo"}, "Transition")
|
||||
self.assertEqual(getXML(toXML, self.font), [
|
||||
'<Transition Test="Foo">',
|
||||
' <NewState value="4660"/>', # 0x1234 = 4660
|
||||
' <Flags value="MarkFirst,DontAdvance,MarkLast"/>',
|
||||
' <ReservedFlags value="0x1FF0"/>',
|
||||
' <Verb value="13"/><!-- ABxCD ⇒ CDxBA -->',
|
||||
'</Transition>',
|
||||
])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
sys.exit(unittest.main())
|
||||
|
Loading…
x
Reference in New Issue
Block a user