• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from fontTools.misc.testTools import FakeFont, getXML, parseXML
2from fontTools.misc.textTools import deHexStr, hexStr
3from fontTools.ttLib import newTable
4import unittest
5
6
7# Glyph Metamorphosis Table Examples
8# Example 1: Non-contextual Glyph Substitution
9# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6mort.html
10# The example given by Apple's 'mort' specification is suboptimally
11# encoded: it uses AAT lookup format 6 even though format 8 would be
12# more compact.  Because our encoder always uses the most compact
13# encoding, this breaks our round-trip testing. Therefore, we changed
14# the example to use GlyphID 13 instead of 12 for the 'parenright'
15# character; the non-contiguous glyph range for the AAT lookup makes
16# format 6 to be most compact.
17MORT_NONCONTEXTUAL_DATA = deHexStr(
18    "0001 0000 "  #  0: Version=1.0
19    "0000 0001 "  #  4: MorphChainCount=1
20    "0000 0001 "  #  8: DefaultFlags=1
21    "0000 0050 "  # 12: StructLength=80
22    "0003 0001 "  # 16: MorphFeatureCount=3, MorphSubtableCount=1
23    "0004 0000 "  # 20: Feature[0].FeatureType=4/VertSubst, .FeatureSetting=on
24    "0000 0001 "  # 24: Feature[0].EnableFlags=0x00000001
25    "FFFF FFFF "  # 28: Feature[0].DisableFlags=0xFFFFFFFF
26    "0004 0001 "  # 32: Feature[1].FeatureType=4/VertSubst, .FeatureSetting=off
27    "0000 0000 "  # 36: Feature[1].EnableFlags=0x00000000
28    "FFFF FFFE "  # 40: Feature[1].DisableFlags=0xFFFFFFFE
29    "0000 0001 "  # 44: Feature[2].FeatureType=0/GlyphEffects, .FeatSetting=off
30    "0000 0000 "  # 48: Feature[2].EnableFlags=0 (required for last feature)
31    "0000 0000 "  # 52: Feature[2].EnableFlags=0 (required for last feature)
32    "0020 "  # 56: Subtable[0].StructLength=32
33    "80 "  # 58: Subtable[0].CoverageFlags=0x80
34    "04 "  # 59: Subtable[0].MorphType=4/NoncontextualMorph
35    "0000 0001 "  # 60: Subtable[0].SubFeatureFlags=0x1
36    "0006 0004 "  # 64: LookupFormat=6, UnitSize=4
37    "0002 0008 "  # 68: NUnits=2, SearchRange=8
38    "0001 0000 "  # 72: EntrySelector=1, RangeShift=0
39    "000B 0087 "  # 76: Glyph=11 (parenleft); Value=135 (parenleft.vertical)
40    "000D 0088 "  # 80: Glyph=13 (parenright); Value=136 (parenright.vertical)
41    "FFFF 0000 "  # 84: Glyph=<end>; Value=0
42)  # 88: <end>
43assert len(MORT_NONCONTEXTUAL_DATA) == 88
44
45
46MORT_NONCONTEXTUAL_XML = [
47    '<Version value="0x00010000"/>',
48    "<!-- MorphChainCount=1 -->",
49    '<MorphChain index="0">',
50    '  <DefaultFlags value="0x00000001"/>',
51    "  <!-- StructLength=80 -->",
52    "  <!-- MorphFeatureCount=3 -->",
53    "  <!-- MorphSubtableCount=1 -->",
54    '  <MorphFeature index="0">',
55    '    <FeatureType value="4"/>',
56    '    <FeatureSetting value="0"/>',
57    '    <EnableFlags value="0x00000001"/>',
58    '    <DisableFlags value="0xFFFFFFFF"/>',
59    "  </MorphFeature>",
60    '  <MorphFeature index="1">',
61    '    <FeatureType value="4"/>',
62    '    <FeatureSetting value="1"/>',
63    '    <EnableFlags value="0x00000000"/>',
64    '    <DisableFlags value="0xFFFFFFFE"/>',
65    "  </MorphFeature>",
66    '  <MorphFeature index="2">',
67    '    <FeatureType value="0"/>',
68    '    <FeatureSetting value="1"/>',
69    '    <EnableFlags value="0x00000000"/>',
70    '    <DisableFlags value="0x00000000"/>',
71    "  </MorphFeature>",
72    '  <MorphSubtable index="0">',
73    "    <!-- StructLength=32 -->",
74    '    <CoverageFlags value="128"/>',
75    "    <!-- MorphType=4 -->",
76    '    <SubFeatureFlags value="0x00000001"/>',
77    "    <NoncontextualMorph>",
78    "      <Substitution>",
79    '        <Lookup glyph="parenleft" value="parenleft.vertical"/>',
80    '        <Lookup glyph="parenright" value="parenright.vertical"/>',
81    "      </Substitution>",
82    "    </NoncontextualMorph>",
83    "  </MorphSubtable>",
84    "</MorphChain>",
85]
86
87
88class MORTNoncontextualGlyphSubstitutionTest(unittest.TestCase):
89    @classmethod
90    def setUpClass(cls):
91        cls.maxDiff = None
92        glyphs = [".notdef"] + ["g.%d" % i for i in range(1, 140)]
93        glyphs[11], glyphs[13] = "parenleft", "parenright"
94        glyphs[135], glyphs[136] = "parenleft.vertical", "parenright.vertical"
95        cls.font = FakeFont(glyphs)
96
97    def test_decompile_toXML(self):
98        table = newTable("mort")
99        table.decompile(MORT_NONCONTEXTUAL_DATA, self.font)
100        self.assertEqual(getXML(table.toXML), MORT_NONCONTEXTUAL_XML)
101
102    def test_compile_fromXML(self):
103        table = newTable("mort")
104        for name, attrs, content in parseXML(MORT_NONCONTEXTUAL_XML):
105            table.fromXML(name, attrs, content, font=self.font)
106        self.assertEqual(
107            hexStr(table.compile(self.font)), hexStr(MORT_NONCONTEXTUAL_DATA)
108        )
109
110
111if __name__ == "__main__":
112    import sys
113
114    sys.exit(unittest.main())
115