[AAT] Support mort
and morx
tables with non-contextual substitutions
Other metamorphosis types are not yet supported and will raise an error upon decompilation. The TTX tool catches the error and continues to emit a hexdump of the table contents, just as before this change.
This commit is contained in:
parent
7bb171ed4a
commit
90f257cc60
@ -71,6 +71,8 @@ def _moduleFinderHint():
|
||||
from . import _l_t_a_g
|
||||
from . import _m_a_x_p
|
||||
from . import _m_e_t_a
|
||||
from . import _m_o_r_t
|
||||
from . import _m_o_r_x
|
||||
from . import _n_a_m_e
|
||||
from . import _o_p_b_d
|
||||
from . import _p_o_s_t
|
||||
|
8
Lib/fontTools/ttLib/tables/_m_o_r_t.py
Normal file
8
Lib/fontTools/ttLib/tables/_m_o_r_t.py
Normal file
@ -0,0 +1,8 @@
|
||||
from __future__ import print_function, division, absolute_import
|
||||
from fontTools.misc.py23 import *
|
||||
from .otBase import BaseTTXConverter
|
||||
|
||||
|
||||
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6mort.html
|
||||
class table__m_o_r_t(BaseTTXConverter):
|
||||
pass
|
8
Lib/fontTools/ttLib/tables/_m_o_r_x.py
Normal file
8
Lib/fontTools/ttLib/tables/_m_o_r_x.py
Normal file
@ -0,0 +1,8 @@
|
||||
from __future__ import print_function, division, absolute_import
|
||||
from fontTools.misc.py23 import *
|
||||
from .otBase import BaseTTXConverter
|
||||
|
||||
|
||||
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6morx.html
|
||||
class table__m_o_r_x(BaseTTXConverter):
|
||||
pass
|
@ -45,6 +45,10 @@ def buildConverters(tableSpec, tableNamespace):
|
||||
converterClass = Struct
|
||||
else:
|
||||
converterClass = eval(tp, tableNamespace, converterMapping)
|
||||
if tp in ('MortChain', 'MortSubtable',
|
||||
'MorxChain', 'MorxSubtable'):
|
||||
tableClass = tableNamespace.get(tp)
|
||||
else:
|
||||
tableClass = tableNamespace.get(tableName)
|
||||
if tableClass is not None:
|
||||
conv = converterClass(name, repeat, aux, tableClass=tableClass)
|
||||
@ -967,8 +971,10 @@ converterMapping = {
|
||||
"VarIdxMapValue": VarIdxMapValue,
|
||||
"VarDataValue": VarDataValue,
|
||||
# AAT
|
||||
"MorphChain": StructWithLength,
|
||||
"MorphSubtable":StructWithLength,
|
||||
"MortChain": StructWithLength,
|
||||
"MortSubtable": StructWithLength,
|
||||
"MorxChain": StructWithLength,
|
||||
"MorxSubtable": StructWithLength,
|
||||
# "Template" types
|
||||
"AATLookup": lambda C: partial(AATLookup, tableClass=C),
|
||||
"OffsetTo": lambda C: partial(Table, tableClass=C),
|
||||
|
@ -1289,25 +1289,50 @@ otData = [
|
||||
|
||||
|
||||
#
|
||||
# morx
|
||||
# mort
|
||||
#
|
||||
|
||||
# TODO: use 'struct' when field.type == field.name
|
||||
('mort', [
|
||||
('Version', 'Version', None, None, 'Version of the mort table.'),
|
||||
('uint32', 'MorphChainCount', None, None, 'Number of metamorphosis chains.'),
|
||||
('MortChain', 'MorphChain', 'MorphChainCount', 0, 'Array of metamorphosis chains.'),
|
||||
]),
|
||||
|
||||
('MortChain', [
|
||||
('Flags32', 'DefaultFlags', None, None, 'The default specification for subtables.'),
|
||||
('uint32', 'StructLength', None, None, 'Total byte count, including this header; must be a multiple of 4.'),
|
||||
('uint16', 'MorphFeatureCount', None, None, 'Number of metamorphosis feature entries.'),
|
||||
('uint16', 'MorphSubtableCount', None, None, 'The number of subtables in the chain.'),
|
||||
('struct', 'MorphFeature', 'MorphFeatureCount', 0, 'Array of metamorphosis features.'),
|
||||
('MortSubtable', 'MorphSubtable', 'MorphSubtableCount', 0, 'Array of metamorphosis subtables.'),
|
||||
]),
|
||||
|
||||
('MortSubtable', [
|
||||
('uint16', 'StructLength', None, None, 'Total subtable length, including this header.'),
|
||||
('uint8', 'CoverageFlags', None, None, 'Most significant byte of coverage flags.'),
|
||||
('uint8', 'MorphType', None, None, 'Subtable type.'),
|
||||
('Flags32', 'SubFeatureFlags', None, None, 'The 32-bit mask identifying which subtable this is (the subtable being executed if the AND of this value and the processed defaultFlags is nonzero).'),
|
||||
('SubStruct', 'SubStruct', None, None, 'SubTable.'),
|
||||
]),
|
||||
|
||||
#
|
||||
# morx
|
||||
#
|
||||
|
||||
('morx', [
|
||||
('uint16', 'Version', None, None, 'Version of the morx table.'),
|
||||
('uint16', 'Reserved', None, None, 'Reserved (set to zero).'),
|
||||
('uint32', 'ChainCount', None, None, 'Number of MorphChains.'),
|
||||
('MorphChain', 'MorphChain', 'ChainCount', 0, 'Array of MorphChains.'),
|
||||
('uint32', 'MorphChainCount', None, None, 'Number of extended metamorphosis chains.'),
|
||||
('MorxChain', 'MorphChain', 'MorphChainCount', 0, 'Array of extended metamorphosis chains.'),
|
||||
]),
|
||||
|
||||
('MorphChain', [
|
||||
('MorxChain', [
|
||||
('Flags32', 'DefaultFlags', None, None, 'The default specification for subtables.'),
|
||||
('uint32', 'StructLength', None, None, 'Total byte count, including this header; must be a multiple of 4.'),
|
||||
('uint32', 'MorphFeatureCount', None, None, 'Number of feature subtable entries.'),
|
||||
('uint32', 'MorphSubtableCount', None, None, 'The number of subtables in the chain.'),
|
||||
('MorphFeature', 'MorphFeature', 'MorphFeatureCount', 0, 'Array of MorphFeatures.'),
|
||||
('MorphSubtable', 'MorphSubtable', 'MorphSubtableCount', 0, 'Array of MorphSubtables.'),
|
||||
('MorphFeature', 'MorphFeature', 'MorphFeatureCount', 0, 'Array of metamorphosis features.'),
|
||||
('MorxSubtable', 'MorphSubtable', 'MorphSubtableCount', 0, 'Array of extended metamorphosis subtables.'),
|
||||
]),
|
||||
|
||||
('MorphFeature', [
|
||||
@ -1320,7 +1345,7 @@ otData = [
|
||||
# Apple TrueType Reference Manual, chapter “The ‘morx’ table”,
|
||||
# section “Metamorphosis Subtables”.
|
||||
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6morx.html
|
||||
('MorphSubtable', [
|
||||
('MorxSubtable', [
|
||||
('uint32', 'StructLength', None, None, 'Total subtable length, including this header.'),
|
||||
('uint8', 'CoverageFlags', None, None, 'Most significant byte of coverage flags.'),
|
||||
('uint16', 'Reserved', None, None, 'Unused.'),
|
||||
@ -1353,7 +1378,7 @@ otData = [
|
||||
]),
|
||||
|
||||
('NoncontextualMorph', [
|
||||
('AATLookup(GlyphID)', 'mapping', None, None, 'The noncontextual glyph substitution table.'),
|
||||
('AATLookup(GlyphID)', 'Substitution', None, None, 'The noncontextual glyph substitution table.'),
|
||||
]),
|
||||
|
||||
('InsertionMorph', [
|
||||
|
@ -991,13 +991,16 @@ def _buildClasses():
|
||||
8: ChainContextPos,
|
||||
9: ExtensionPos,
|
||||
},
|
||||
'mort': {
|
||||
4: NoncontextualMorph,
|
||||
},
|
||||
'morx': {
|
||||
0: RearrangementMorph,
|
||||
1: ContextualMorph,
|
||||
2: LigatureMorph,
|
||||
# 0: RearrangementMorph,
|
||||
# 1: ContextualMorph,
|
||||
# 2: LigatureMorph,
|
||||
# 3: Reserved,
|
||||
4: NoncontextualMorph,
|
||||
5: InsertionMorph,
|
||||
# 5: InsertionMorph,
|
||||
},
|
||||
}
|
||||
lookupTypes['JSTF'] = lookupTypes['GPOS'] # JSTF contains GPOS
|
||||
|
@ -102,8 +102,8 @@ The following tables are currently supported:
|
||||
OS/2, SING, STAT, SVG, TSI0, TSI1, TSI2, TSI3, TSI5, TSIB, TSID,
|
||||
TSIJ, TSIP, TSIS, TSIV, TTFA, VDMX, VORG, VVAR, avar, bsln, cmap,
|
||||
cvar, cvt, feat, fpgm, fvar, gasp, glyf, gvar, hdmx, head, hhea,
|
||||
hmtx, kern, lcar, loca, ltag, maxp, meta, name, opbd, post, prep,
|
||||
prop, sbix, trak, vhea and vmtx
|
||||
hmtx, kern, lcar, loca, ltag, maxp, meta, mort, morx, name, opbd,
|
||||
post, prep, prop, sbix, trak, vhea and vmtx
|
||||
.. end table list
|
||||
|
||||
Other tables are dumped as hexadecimal data.
|
||||
|
115
Tests/ttLib/tables/_m_o_r_t_test.py
Normal file
115
Tests/ttLib/tables/_m_o_r_t_test.py
Normal file
@ -0,0 +1,115 @@
|
||||
from __future__ import print_function, division, absolute_import, unicode_literals
|
||||
from fontTools.misc.py23 import *
|
||||
from fontTools.misc.testTools import FakeFont, getXML, parseXML
|
||||
from fontTools.misc.textTools import deHexStr, hexStr
|
||||
from fontTools.ttLib import newTable
|
||||
import unittest
|
||||
|
||||
|
||||
# Glyph Metamorphosis Table Examples
|
||||
# Example 1: Non-contextual Glyph Substitution
|
||||
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6mort.html
|
||||
# The example given by Apple's 'mort' specification is suboptimally
|
||||
# encoded: it uses AAT lookup format 6 even though format 8 would be
|
||||
# more compact. Because our encoder always uses the most compact
|
||||
# encoding, this breaks our round-trip testing. Therefore, we changed
|
||||
# the example to use GlyphID 13 instead of 12 for the 'parenright'
|
||||
# character; the non-contiguous glyph range for the AAT lookup makes
|
||||
# format 6 to be most compact.
|
||||
MORT_NONCONTEXTUAL_DATA = deHexStr(
|
||||
'0001 0000 ' # 0: Version=1.0
|
||||
'0000 0001 ' # 4: MorphChainCount=1
|
||||
'0000 0001 ' # 8: DefaultFlags=1
|
||||
'0000 0050 ' # 12: StructLength=80
|
||||
'0003 0001 ' # 16: MorphFeatureCount=3, MorphSubtableCount=1
|
||||
'0004 0000 ' # 20: Feature[0].FeatureType=4/VertSubst, .FeatureSetting=on
|
||||
'0000 0001 ' # 24: Feature[0].EnableFlags=0x00000001
|
||||
'FFFF FFFF ' # 28: Feature[0].DisableFlags=0xFFFFFFFF
|
||||
'0004 0001 ' # 32: Feature[1].FeatureType=4/VertSubst, .FeatureSetting=off
|
||||
'0000 0000 ' # 36: Feature[1].EnableFlags=0x00000000
|
||||
'FFFF FFFE ' # 40: Feature[1].DisableFlags=0xFFFFFFFE
|
||||
'0000 0001 ' # 44: Feature[2].FeatureType=0/GlyphEffects, .FeatSetting=off
|
||||
'0000 0000 ' # 48: Feature[2].EnableFlags=0 (required for last feature)
|
||||
'0000 0000 ' # 52: Feature[2].EnableFlags=0 (required for last feature)
|
||||
'0020 ' # 56: Subtable[0].StructLength=32
|
||||
'80 ' # 58: Subtable[0].CoverageFlags=0x80
|
||||
'04 ' # 59: Subtable[0].MorphType=4/NoncontextualMorph
|
||||
'0000 0001 ' # 60: Subtable[0].SubFeatureFlags=0x1
|
||||
'0006 0004 ' # 64: LookupFormat=6, UnitSize=4
|
||||
'0002 0008 ' # 68: NUnits=2, SearchRange=8
|
||||
'0001 0000 ' # 72: EntrySelector=1, RangeShift=0
|
||||
'000B 0087 ' # 76: Glyph=11 (parenleft); Value=135 (parenleft.vertical)
|
||||
'000D 0088 ' # 80: Glyph=13 (parenright); Value=136 (parenright.vertical)
|
||||
'FFFF 0000 ' # 84: Glyph=<end>; Value=0
|
||||
) # 88: <end>
|
||||
assert len(MORT_NONCONTEXTUAL_DATA) == 88
|
||||
|
||||
|
||||
MORT_NONCONTEXTUAL_XML = [
|
||||
'<Version value="0x00010000"/>',
|
||||
'<!-- MorphChainCount=1 -->',
|
||||
'<MorphChain index="0">',
|
||||
' <DefaultFlags value="0x00000001"/>',
|
||||
' <!-- StructLength=80 -->',
|
||||
' <!-- MorphFeatureCount=3 -->',
|
||||
' <!-- MorphSubtableCount=1 -->',
|
||||
' <MorphFeature index="0">',
|
||||
' <FeatureType value="4"/>',
|
||||
' <FeatureSetting value="0"/>',
|
||||
' <EnableFlags value="0x00000001"/>',
|
||||
' <DisableFlags value="0xFFFFFFFF"/>',
|
||||
' </MorphFeature>',
|
||||
' <MorphFeature index="1">',
|
||||
' <FeatureType value="4"/>',
|
||||
' <FeatureSetting value="1"/>',
|
||||
' <EnableFlags value="0x00000000"/>',
|
||||
' <DisableFlags value="0xFFFFFFFE"/>',
|
||||
' </MorphFeature>',
|
||||
' <MorphFeature index="2">',
|
||||
' <FeatureType value="0"/>',
|
||||
' <FeatureSetting value="1"/>',
|
||||
' <EnableFlags value="0x00000000"/>',
|
||||
' <DisableFlags value="0x00000000"/>',
|
||||
' </MorphFeature>',
|
||||
' <MorphSubtable index="0">',
|
||||
' <!-- StructLength=32 -->',
|
||||
' <CoverageFlags value="128"/>',
|
||||
' <!-- MorphType=4 -->',
|
||||
' <SubFeatureFlags value="0x00000001"/>',
|
||||
' <NoncontextualMorph>',
|
||||
' <Substitution>',
|
||||
' <Lookup glyph="parenleft" value="parenleft.vertical"/>',
|
||||
' <Lookup glyph="parenright" value="parenright.vertical"/>',
|
||||
' </Substitution>',
|
||||
' </NoncontextualMorph>',
|
||||
' </MorphSubtable>',
|
||||
'</MorphChain>',
|
||||
]
|
||||
|
||||
|
||||
class MORTNoncontextualGlyphSubstitutionTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.maxDiff = None
|
||||
glyphs = ['.notdef'] + ['g.%d' % i for i in range (1, 140)]
|
||||
glyphs[11], glyphs[13] = 'parenleft', 'parenright'
|
||||
glyphs[135], glyphs[136] = 'parenleft.vertical', 'parenright.vertical'
|
||||
cls.font = FakeFont(glyphs)
|
||||
|
||||
def test_decompile_toXML(self):
|
||||
table = newTable('mort')
|
||||
table.decompile(MORT_NONCONTEXTUAL_DATA, self.font)
|
||||
self.assertEqual(getXML(table.toXML), MORT_NONCONTEXTUAL_XML)
|
||||
|
||||
def test_compile_fromXML(self):
|
||||
table = newTable('mort')
|
||||
for name, attrs, content in parseXML(MORT_NONCONTEXTUAL_XML):
|
||||
table.fromXML(name, attrs, content, font=self.font)
|
||||
self.assertEqual(hexStr(table.compile(self.font)),
|
||||
hexStr(MORT_NONCONTEXTUAL_DATA))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
sys.exit(unittest.main())
|
114
Tests/ttLib/tables/_m_o_r_x_test.py
Normal file
114
Tests/ttLib/tables/_m_o_r_x_test.py
Normal file
@ -0,0 +1,114 @@
|
||||
from __future__ import print_function, division, absolute_import, unicode_literals
|
||||
from fontTools.misc.py23 import *
|
||||
from fontTools.misc.testTools import FakeFont, getXML, parseXML
|
||||
from fontTools.misc.textTools import deHexStr, hexStr
|
||||
from fontTools.ttLib import newTable
|
||||
import unittest
|
||||
|
||||
|
||||
# A simple 'morx' table with non-contextual glyph substitution.
|
||||
# Unfortunately, the Apple spec for 'morx' does not contain a complete example.
|
||||
# The test case has therefore been adapted from the example 'mort' table in
|
||||
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6mort.html
|
||||
MORX_NONCONTEXTUAL_DATA = deHexStr(
|
||||
'0002 0000 ' # 0: Version=2, Reserved=0
|
||||
'0000 0001 ' # 4: MorphChainCount=1
|
||||
'0000 0001 ' # 8: DefaultFlags=1
|
||||
'0000 0058 ' # 12: StructLength=88
|
||||
'0000 0003 ' # 16: MorphFeatureCount=3
|
||||
'0000 0001 ' # 20: MorphSubtableCount=1
|
||||
'0004 0000 ' # 24: Feature[0].FeatureType=4/VertSubst, .FeatureSetting=on
|
||||
'0000 0001 ' # 28: Feature[0].EnableFlags=0x00000001
|
||||
'FFFF FFFF ' # 32: Feature[0].DisableFlags=0xFFFFFFFF
|
||||
'0004 0001 ' # 36: Feature[1].FeatureType=4/VertSubst, .FeatureSetting=off
|
||||
'0000 0000 ' # 40: Feature[1].EnableFlags=0x00000000
|
||||
'FFFF FFFE ' # 44: Feature[1].DisableFlags=0xFFFFFFFE
|
||||
'0000 0001 ' # 48: Feature[2].FeatureType=0/GlyphEffects, .FeatSetting=off
|
||||
'0000 0000 ' # 52: Feature[2].EnableFlags=0 (required for last feature)
|
||||
'0000 0000 ' # 56: Feature[2].EnableFlags=0 (required for last feature)
|
||||
'0000 0024 ' # 60: Subtable[0].StructLength=36
|
||||
'80 ' # 64: Subtable[0].CoverageFlags=0x80
|
||||
'00 00 ' # 65: Subtable[0].Reserved=0
|
||||
'04 ' # 67: Subtable[0].MorphType=4/NoncontextualMorph
|
||||
'0000 0001 ' # 68: Subtable[0].SubFeatureFlags=0x1
|
||||
'0006 0004 ' # 72: LookupFormat=6, UnitSize=4
|
||||
'0002 0008 ' # 76: NUnits=2, SearchRange=8
|
||||
'0001 0000 ' # 80: EntrySelector=1, RangeShift=0
|
||||
'000B 0087 ' # 84: Glyph=11 (parenleft); Value=135 (parenleft.vertical)
|
||||
'000D 0088 ' # 88: Glyph=13 (parenright); Value=136 (parenright.vertical)
|
||||
'FFFF 0000 ' # 92: Glyph=<end>; Value=0
|
||||
) # 96: <end>
|
||||
assert len(MORX_NONCONTEXTUAL_DATA) == 96
|
||||
|
||||
|
||||
MORX_NONCONTEXTUAL_XML = [
|
||||
'<Version value="2"/>',
|
||||
'<Reserved value="0"/>',
|
||||
'<!-- MorphChainCount=1 -->',
|
||||
'<MorphChain index="0">',
|
||||
' <DefaultFlags value="0x00000001"/>',
|
||||
' <!-- StructLength=88 -->',
|
||||
' <!-- MorphFeatureCount=3 -->',
|
||||
' <!-- MorphSubtableCount=1 -->',
|
||||
' <MorphFeature index="0">',
|
||||
' <FeatureType value="4"/>',
|
||||
' <FeatureSetting value="0"/>',
|
||||
' <EnableFlags value="0x00000001"/>',
|
||||
' <DisableFlags value="0xFFFFFFFF"/>',
|
||||
' </MorphFeature>',
|
||||
' <MorphFeature index="1">',
|
||||
' <FeatureType value="4"/>',
|
||||
' <FeatureSetting value="1"/>',
|
||||
' <EnableFlags value="0x00000000"/>',
|
||||
' <DisableFlags value="0xFFFFFFFE"/>',
|
||||
' </MorphFeature>',
|
||||
' <MorphFeature index="2">',
|
||||
' <FeatureType value="0"/>',
|
||||
' <FeatureSetting value="1"/>',
|
||||
' <EnableFlags value="0x00000000"/>',
|
||||
' <DisableFlags value="0x00000000"/>',
|
||||
' </MorphFeature>',
|
||||
' <MorphSubtable index="0">',
|
||||
' <!-- StructLength=36 -->',
|
||||
' <CoverageFlags value="128"/>',
|
||||
' <Reserved value="0"/>',
|
||||
' <!-- MorphType=4 -->',
|
||||
' <SubFeatureFlags value="0x00000001"/>',
|
||||
' <NoncontextualMorph>',
|
||||
' <Substitution>',
|
||||
' <Lookup glyph="parenleft" value="parenleft.vertical"/>',
|
||||
' <Lookup glyph="parenright" value="parenright.vertical"/>',
|
||||
' </Substitution>',
|
||||
' </NoncontextualMorph>',
|
||||
' </MorphSubtable>',
|
||||
'</MorphChain>',
|
||||
]
|
||||
|
||||
|
||||
class MORXNoncontextualGlyphSubstitutionTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.maxDiff = None
|
||||
glyphs = ['.notdef'] + ['g.%d' % i for i in range (1, 140)]
|
||||
glyphs[11], glyphs[13] = 'parenleft', 'parenright'
|
||||
glyphs[135], glyphs[136] = 'parenleft.vertical', 'parenright.vertical'
|
||||
cls.font = FakeFont(glyphs)
|
||||
|
||||
def test_decompile_toXML(self):
|
||||
table = newTable('morx')
|
||||
table.decompile(MORX_NONCONTEXTUAL_DATA, self.font)
|
||||
self.assertEqual(getXML(table.toXML), MORX_NONCONTEXTUAL_XML)
|
||||
|
||||
def test_compile_fromXML(self):
|
||||
table = newTable('morx')
|
||||
for name, attrs, content in parseXML(MORX_NONCONTEXTUAL_XML):
|
||||
table.fromXML(name, attrs, content, font=self.font)
|
||||
self.assertEqual(hexStr(table.compile(self.font)),
|
||||
hexStr(MORX_NONCONTEXTUAL_DATA))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
sys.exit(unittest.main())
|
||||
|
Loading…
x
Reference in New Issue
Block a user