Home | History | Annotate | Download | only in tables
      1 # coding: utf-8
      2 from __future__ import print_function, division, absolute_import, unicode_literals
      3 from fontTools.misc.py23 import *
      4 from fontTools.misc.testTools import FakeFont, getXML, parseXML
      5 from fontTools.misc.textTools import deHexStr, hexStr
      6 from fontTools.ttLib import newTable
      7 import unittest
      8 
      9 
     10 # A simple 'morx' table with non-contextual glyph substitution.
     11 # Unfortunately, the Apple spec for 'morx' does not contain a complete example.
     12 # The test case has therefore been adapted from the example 'mort' table in
     13 # https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6mort.html
     14 MORX_NONCONTEXTUAL_DATA = deHexStr(
     15     '0002 0000 '  #  0: Version=2, Reserved=0
     16     '0000 0001 '  #  4: MorphChainCount=1
     17     '0000 0001 '  #  8: DefaultFlags=1
     18     '0000 0058 '  # 12: StructLength=88
     19     '0000 0003 '  # 16: MorphFeatureCount=3
     20     '0000 0001 '  # 20: MorphSubtableCount=1
     21     '0004 0000 '  # 24: Feature[0].FeatureType=4/VertSubst, .FeatureSetting=on
     22     '0000 0001 '  # 28: Feature[0].EnableFlags=0x00000001
     23     'FFFF FFFF '  # 32: Feature[0].DisableFlags=0xFFFFFFFF
     24     '0004 0001 '  # 36: Feature[1].FeatureType=4/VertSubst, .FeatureSetting=off
     25     '0000 0000 '  # 40: Feature[1].EnableFlags=0x00000000
     26     'FFFF FFFE '  # 44: Feature[1].DisableFlags=0xFFFFFFFE
     27     '0000 0001 '  # 48: Feature[2].FeatureType=0/GlyphEffects, .FeatSetting=off
     28     '0000 0000 '  # 52: Feature[2].EnableFlags=0 (required for last feature)
     29     '0000 0000 '  # 56: Feature[2].EnableFlags=0 (required for last feature)
     30     '0000 0024 '  # 60: Subtable[0].StructLength=36
     31     '80 '         # 64: Subtable[0].CoverageFlags=0x80
     32     '00 00 '      # 65: Subtable[0].Reserved=0
     33     '04 '         # 67: Subtable[0].MorphType=4/NoncontextualMorph
     34     '0000 0001 '  # 68: Subtable[0].SubFeatureFlags=0x1
     35     '0006 0004 '  # 72: LookupFormat=6, UnitSize=4
     36     '0002 0008 '  # 76: NUnits=2, SearchRange=8
     37     '0001 0000 '  # 80: EntrySelector=1, RangeShift=0
     38     '000B 0087 '  # 84: Glyph=11 (parenleft); Value=135 (parenleft.vertical)
     39     '000D 0088 '  # 88: Glyph=13 (parenright); Value=136 (parenright.vertical)
     40     'FFFF 0000 '  # 92: Glyph=<end>; Value=0
     41 )                 # 96: <end>
     42 assert len(MORX_NONCONTEXTUAL_DATA) == 96
     43 
     44 
     45 MORX_NONCONTEXTUAL_XML = [
     46     '<Version value="2"/>',
     47     '<Reserved value="0"/>',
     48     '<!-- MorphChainCount=1 -->',
     49     '<MorphChain index="0">',
     50     '  <DefaultFlags value="0x00000001"/>',
     51     '  <!-- StructLength=88 -->',
     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=36 -->',
     74     '    <TextDirection value="Vertical"/>',
     75     '    <ProcessingOrder value="LayoutOrder"/>',
     76     '    <!-- MorphType=4 -->',
     77     '    <SubFeatureFlags value="0x00000001"/>',
     78     '    <NoncontextualMorph>',
     79     '      <Substitution>',
     80     '        <Lookup glyph="parenleft" value="parenleft.vertical"/>',
     81     '        <Lookup glyph="parenright" value="parenright.vertical"/>',
     82     '      </Substitution>',
     83     '    </NoncontextualMorph>',
     84     '  </MorphSubtable>',
     85     '</MorphChain>',
     86 ]
     87 
     88 
     89 MORX_REARRANGEMENT_DATA = deHexStr(
     90     '0002 0000 '  #  0: Version=2, Reserved=0
     91     '0000 0001 '  #  4: MorphChainCount=1
     92     '0000 0001 '  #  8: DefaultFlags=1
     93     '0000 0078 '  # 12: StructLength=120 (+8=128)
     94     '0000 0000 '  # 16: MorphFeatureCount=0
     95     '0000 0001 '  # 20: MorphSubtableCount=1
     96     '0000 0068 '  # 24: Subtable[0].StructLength=104 (+24=128)
     97     '80 '         # 28: Subtable[0].CoverageFlags=0x80
     98     '00 00 '      # 29: Subtable[0].Reserved=0
     99     '00 '         # 31: Subtable[0].MorphType=0/RearrangementMorph
    100     '0000 0001 '  # 32: Subtable[0].SubFeatureFlags=0x1
    101     '0000 0006 '  # 36: STXHeader.ClassCount=6
    102     '0000 0010 '  # 40: STXHeader.ClassTableOffset=16 (+36=52)
    103     '0000 0028 '  # 44: STXHeader.StateArrayOffset=40 (+36=76)
    104     '0000 004C '  # 48: STXHeader.EntryTableOffset=76 (+36=112)
    105     '0006 0004 '  # 52: ClassTable.LookupFormat=6, .UnitSize=4
    106     '0002 0008 '  # 56:   .NUnits=2, .SearchRange=8
    107     '0001 0000 '  # 60:   .EntrySelector=1, .RangeShift=0
    108     '0001 0005 '  # 64:   Glyph=A; Class=5
    109     '0003 0004 '  # 68:   Glyph=C; Class=4
    110     'FFFF 0000 '  # 72:   Glyph=<end>; Value=0
    111     '0000 0001 0002 0003 0002 0001 '  #  76: State[0][0..5]
    112     '0003 0003 0003 0003 0003 0003 '  #  88: State[1][0..5]
    113     '0001 0003 0003 0003 0002 0002 '  # 100: State[2][0..5]
    114     '0002 FFFF '  # 112: Entries[0].NewState=2, .Flags=0xFFFF
    115     '0001 A00D '  # 116: Entries[1].NewState=1, .Flags=0xA00D
    116     '0000 8006 '  # 120: Entries[2].NewState=0, .Flags=0x8006
    117     '0002 0000 '  # 124: Entries[3].NewState=2, .Flags=0x0000
    118 )                 # 128: <end>
    119 assert len(MORX_REARRANGEMENT_DATA) == 128, len(MORX_REARRANGEMENT_DATA)
    120 
    121 
    122 MORX_REARRANGEMENT_XML = [
    123     '<Version value="2"/>',
    124     '<Reserved value="0"/>',
    125     '<!-- MorphChainCount=1 -->',
    126     '<MorphChain index="0">',
    127     '  <DefaultFlags value="0x00000001"/>',
    128     '  <!-- StructLength=120 -->',
    129     '  <!-- MorphFeatureCount=0 -->',
    130     '  <!-- MorphSubtableCount=1 -->',
    131     '  <MorphSubtable index="0">',
    132     '    <!-- StructLength=104 -->',
    133     '    <TextDirection value="Vertical"/>',
    134     '    <ProcessingOrder value="LayoutOrder"/>',
    135     '    <!-- MorphType=0 -->',
    136     '    <SubFeatureFlags value="0x00000001"/>',
    137     '    <RearrangementMorph>',
    138     '      <StateTable>',
    139     '        <!-- GlyphClassCount=6 -->',
    140     '        <GlyphClass glyph="A" value="5"/>',
    141     '        <GlyphClass glyph="C" value="4"/>',
    142     '        <State index="0">',
    143     '          <Transition onGlyphClass="0">',
    144     '            <NewState value="2"/>',
    145     '            <Flags value="MarkFirst,DontAdvance,MarkLast"/>',
    146     '            <ReservedFlags value="0x1FF0"/>',
    147     '            <Verb value="15"/><!-- ABxCD  DCxBA -->',
    148     '          </Transition>',
    149     '          <Transition onGlyphClass="1">',
    150     '            <NewState value="1"/>',
    151     '            <Flags value="MarkFirst,MarkLast"/>',
    152     '            <Verb value="13"/><!-- ABxCD  CDxBA -->',
    153     '          </Transition>',
    154     '          <Transition onGlyphClass="2">',
    155     '            <NewState value="0"/>',
    156     '            <Flags value="MarkFirst"/>',
    157     '            <Verb value="6"/><!-- xCD  CDx -->',
    158     '          </Transition>',
    159     '          <Transition onGlyphClass="3">',
    160     '            <NewState value="2"/>',
    161     '            <Verb value="0"/><!-- no change -->',
    162     '          </Transition>',
    163     '          <Transition onGlyphClass="4">',
    164     '            <NewState value="0"/>',
    165     '            <Flags value="MarkFirst"/>',
    166     '            <Verb value="6"/><!-- xCD  CDx -->',
    167     '          </Transition>',
    168     '          <Transition onGlyphClass="5">',
    169     '            <NewState value="1"/>',
    170     '            <Flags value="MarkFirst,MarkLast"/>',
    171     '            <Verb value="13"/><!-- ABxCD  CDxBA -->',
    172     '          </Transition>',
    173     '        </State>',
    174     '        <State index="1">',
    175     '          <Transition onGlyphClass="0">',
    176     '            <NewState value="2"/>',
    177     '            <Verb value="0"/><!-- no change -->',
    178     '          </Transition>',
    179     '          <Transition onGlyphClass="1">',
    180     '            <NewState value="2"/>',
    181     '            <Verb value="0"/><!-- no change -->',
    182     '          </Transition>',
    183     '          <Transition onGlyphClass="2">',
    184     '            <NewState value="2"/>',
    185     '            <Verb value="0"/><!-- no change -->',
    186     '          </Transition>',
    187     '          <Transition onGlyphClass="3">',
    188     '            <NewState value="2"/>',
    189     '            <Verb value="0"/><!-- no change -->',
    190     '          </Transition>',
    191     '          <Transition onGlyphClass="4">',
    192     '            <NewState value="2"/>',
    193     '            <Verb value="0"/><!-- no change -->',
    194     '          </Transition>',
    195     '          <Transition onGlyphClass="5">',
    196     '            <NewState value="2"/>',
    197     '            <Verb value="0"/><!-- no change -->',
    198     '          </Transition>',
    199     '        </State>',
    200     '        <State index="2">',
    201     '          <Transition onGlyphClass="0">',
    202     '            <NewState value="1"/>',
    203     '            <Flags value="MarkFirst,MarkLast"/>',
    204     '            <Verb value="13"/><!-- ABxCD  CDxBA -->',
    205     '          </Transition>',
    206     '          <Transition onGlyphClass="1">',
    207     '            <NewState value="2"/>',
    208     '            <Verb value="0"/><!-- no change -->',
    209     '          </Transition>',
    210     '          <Transition onGlyphClass="2">',
    211     '            <NewState value="2"/>',
    212     '            <Verb value="0"/><!-- no change -->',
    213     '          </Transition>',
    214     '          <Transition onGlyphClass="3">',
    215     '            <NewState value="2"/>',
    216     '            <Verb value="0"/><!-- no change -->',
    217     '          </Transition>',
    218     '          <Transition onGlyphClass="4">',
    219     '            <NewState value="0"/>',
    220     '            <Flags value="MarkFirst"/>',
    221     '            <Verb value="6"/><!-- xCD  CDx -->',
    222     '          </Transition>',
    223     '          <Transition onGlyphClass="5">',
    224     '            <NewState value="0"/>',
    225     '            <Flags value="MarkFirst"/>',
    226     '            <Verb value="6"/><!-- xCD  CDx -->',
    227     '          </Transition>',
    228     '        </State>',
    229     '      </StateTable>',
    230     '    </RearrangementMorph>',
    231     '  </MorphSubtable>',
    232     '</MorphChain>',
    233 ]
    234 
    235 
    236 # Taken from Example 1: A contextal substituation table in
    237 # https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6morx.html
    238 # as retrieved on 2017-09-05.
    239 #
    240 # Compared to the example table in Apples specification, weve
    241 # made the following changes:
    242 #
    243 # * at offsets 0..35, weve prepended 36 bytes of boilerplate
    244 #   to make the data a structurally valid morx table;
    245 #
    246 # * at offset 36 (offset 0 in Apples document), weve changed
    247 #   the number of glyph classes from 5 to 6 because the encoded
    248 #   finite-state machine has transitions for six different glyph
    249 #   classes (0..5);
    250 #
    251 # * at offset 52 (offset 16 in Apples document), weve replaced
    252 #   the presumably leftover XXX mark by an actual data offset;
    253 #
    254 # * at offset 72 (offset 36 in Apples document), weve changed
    255 #   the input GlyphID from 51 to 52. With the original value of 51,
    256 #   the glyph class lookup table can be encoded with equally many
    257 #   bytes in either format 2 or 6; after changing the GlyphID to 52,
    258 #   the most compact encoding is lookup format 6, as used in Apples
    259 #   example;
    260 #
    261 # * at offset 90 (offset 54 in Apples document), weve changed
    262 #   the value for the lookup end-of-table marker from 1 to 0.
    263 #   Fonttools always uses zero for this value, whereas Apples
    264 #   spec examples are inconsistently using one of {0, 1, 0xFFFF}
    265 #   for this filler value;
    266 #
    267 # * at offset 172 (offset 136 in Apples document), weve again changed
    268 #   the input GlyphID from 51 to 52, for the same reason as above.
    269 #
    270 # TODO: Ask Apple to fix Example 1 in the morx specification.
    271 MORX_CONTEXTUAL_DATA = deHexStr(
    272     '0002 0000 '  #  0: Version=2, Reserved=0
    273     '0000 0001 '  #  4: MorphChainCount=1
    274     '0000 0001 '  #  8: DefaultFlags=1
    275     '0000 00B4 '  # 12: StructLength=180 (+8=188)
    276     '0000 0000 '  # 16: MorphFeatureCount=0
    277     '0000 0001 '  # 20: MorphSubtableCount=1
    278     '0000 00A4 '  # 24: Subtable[0].StructLength=164 (+24=188)
    279     '80 '         # 28: Subtable[0].CoverageFlags=0x80
    280     '00 00 '      # 29: Subtable[0].Reserved=0
    281     '01 '         # 31: Subtable[0].MorphType=1/ContextualMorph
    282     '0000 0001 '  # 32: Subtable[0].SubFeatureFlags=0x1
    283     '0000 0006 '  # 36: STXHeader.ClassCount=6
    284     '0000 0014 '  # 40: STXHeader.ClassTableOffset=20 (+36=56)
    285     '0000 0038 '  # 44: STXHeader.StateArrayOffset=56 (+36=92)
    286     '0000 005C '  # 48: STXHeader.EntryTableOffset=92 (+36=128)
    287     '0000 0074 '  # 52: STXHeader.PerGlyphTableOffset=116 (+36=152)
    288 
    289     # Glyph class table.
    290     '0006 0004 '  # 56: ClassTable.LookupFormat=6, .UnitSize=4
    291     '0005 0010 '  # 60:   .NUnits=5, .SearchRange=16
    292     '0002 0004 '  # 64:   .EntrySelector=2, .RangeShift=4
    293     '0032 0004 '  # 68:   Glyph=50; Class=4
    294     '0034 0004 '  # 72:   Glyph=52; Class=4
    295     '0050 0005 '  # 76:   Glyph=80; Class=5
    296     '00C9 0004 '  # 80:   Glyph=201; Class=4
    297     '00CA 0004 '  # 84:   Glyph=202; Class=4
    298     'FFFF 0000 '  # 88:   Glyph=<end>; Value=<filler>
    299 
    300     # State array.
    301     '0000 0000 0000 0000 0000 0001 '  #  92: State[0][0..5]
    302     '0000 0000 0000 0000 0000 0001 '  # 104: State[1][0..5]
    303     '0000 0000 0000 0000 0002 0001 '  # 116: State[2][0..5]
    304 
    305     # Entry table.
    306     '0000 0000 '  # 128: Entries[0].NewState=0, .Flags=0
    307     'FFFF FFFF '  # 132: Entries[0].MarkSubst=None, .CurSubst=None
    308     '0002 0000 '  # 136: Entries[1].NewState=2, .Flags=0
    309     'FFFF FFFF '  # 140: Entries[1].MarkSubst=None, .CurSubst=None
    310     '0000 0000 '  # 144: Entries[2].NewState=0, .Flags=0
    311     'FFFF 0000 '  # 148: Entries[2].MarkSubst=None, .CurSubst=PerGlyph #0
    312                   # 152: <no padding needed for 4-byte alignment>
    313 
    314     # Per-glyph lookup tables.
    315     '0000 0004 '  # 152: Offset from this point to per-glyph lookup #0.
    316 
    317     # Per-glyph lookup #0.
    318     '0006 0004 '  # 156: ClassTable.LookupFormat=6, .UnitSize=4
    319     '0004 0010 '  # 160:   .NUnits=4, .SearchRange=16
    320     '0002 0000 '  # 164:   .EntrySelector=2, .RangeShift=0
    321     '0032 0258 '  # 168:   Glyph=50; ReplacementGlyph=600
    322     '0034 0259 '  # 172:   Glyph=52; ReplacementGlyph=601
    323     '00C9 025A '  # 176:   Glyph=201; ReplacementGlyph=602
    324     '00CA 0384 '  # 180:   Glyph=202; ReplacementGlyph=900
    325     'FFFF 0000 '  # 184:   Glyph=<end>; Value=<filler>
    326 
    327 )                 # 188: <end>
    328 assert len(MORX_CONTEXTUAL_DATA) == 188, len(MORX_CONTEXTUAL_DATA)
    329 
    330 
    331 MORX_CONTEXTUAL_XML = [
    332     '<Version value="2"/>',
    333     '<Reserved value="0"/>',
    334     '<!-- MorphChainCount=1 -->',
    335     '<MorphChain index="0">',
    336     '  <DefaultFlags value="0x00000001"/>',
    337     '  <!-- StructLength=180 -->',
    338     '  <!-- MorphFeatureCount=0 -->',
    339     '  <!-- MorphSubtableCount=1 -->',
    340     '  <MorphSubtable index="0">',
    341     '    <!-- StructLength=164 -->',
    342     '    <TextDirection value="Vertical"/>',
    343     '    <ProcessingOrder value="LayoutOrder"/>',
    344     '    <!-- MorphType=1 -->',
    345     '    <SubFeatureFlags value="0x00000001"/>',
    346     '    <ContextualMorph>',
    347     '      <StateTable>',
    348     '        <!-- GlyphClassCount=6 -->',
    349     '        <GlyphClass glyph="A" value="4"/>',
    350     '        <GlyphClass glyph="B" value="4"/>',
    351     '        <GlyphClass glyph="C" value="5"/>',
    352     '        <GlyphClass glyph="X" value="4"/>',
    353     '        <GlyphClass glyph="Y" value="4"/>',
    354     '        <State index="0">',
    355     '          <Transition onGlyphClass="0">',
    356     '            <NewState value="0"/>',
    357     '            <MarkIndex value="65535"/>',
    358     '            <CurrentIndex value="65535"/>',
    359     '          </Transition>',
    360     '          <Transition onGlyphClass="1">',
    361     '            <NewState value="0"/>',
    362     '            <MarkIndex value="65535"/>',
    363     '            <CurrentIndex value="65535"/>',
    364     '          </Transition>',
    365     '          <Transition onGlyphClass="2">',
    366     '            <NewState value="0"/>',
    367     '            <MarkIndex value="65535"/>',
    368     '            <CurrentIndex value="65535"/>',
    369     '          </Transition>',
    370     '          <Transition onGlyphClass="3">',
    371     '            <NewState value="0"/>',
    372     '            <MarkIndex value="65535"/>',
    373     '            <CurrentIndex value="65535"/>',
    374     '          </Transition>',
    375     '          <Transition onGlyphClass="4">',
    376     '            <NewState value="0"/>',
    377     '            <MarkIndex value="65535"/>',
    378     '            <CurrentIndex value="65535"/>',
    379     '          </Transition>',
    380     '          <Transition onGlyphClass="5">',
    381     '            <NewState value="2"/>',
    382     '            <MarkIndex value="65535"/>',
    383     '            <CurrentIndex value="65535"/>',
    384     '          </Transition>',
    385     '        </State>',
    386     '        <State index="1">',
    387     '          <Transition onGlyphClass="0">',
    388     '            <NewState value="0"/>',
    389     '            <MarkIndex value="65535"/>',
    390     '            <CurrentIndex value="65535"/>',
    391     '          </Transition>',
    392     '          <Transition onGlyphClass="1">',
    393     '            <NewState value="0"/>',
    394     '            <MarkIndex value="65535"/>',
    395     '            <CurrentIndex value="65535"/>',
    396     '          </Transition>',
    397     '          <Transition onGlyphClass="2">',
    398     '            <NewState value="0"/>',
    399     '            <MarkIndex value="65535"/>',
    400     '            <CurrentIndex value="65535"/>',
    401     '          </Transition>',
    402     '          <Transition onGlyphClass="3">',
    403     '            <NewState value="0"/>',
    404     '            <MarkIndex value="65535"/>',
    405     '            <CurrentIndex value="65535"/>',
    406     '          </Transition>',
    407     '          <Transition onGlyphClass="4">',
    408     '            <NewState value="0"/>',
    409     '            <MarkIndex value="65535"/>',
    410     '            <CurrentIndex value="65535"/>',
    411     '          </Transition>',
    412     '          <Transition onGlyphClass="5">',
    413     '            <NewState value="2"/>',
    414     '            <MarkIndex value="65535"/>',
    415     '            <CurrentIndex value="65535"/>',
    416     '          </Transition>',
    417     '        </State>',
    418     '        <State index="2">',
    419     '          <Transition onGlyphClass="0">',
    420     '            <NewState value="0"/>',
    421     '            <MarkIndex value="65535"/>',
    422     '            <CurrentIndex value="65535"/>',
    423     '          </Transition>',
    424     '          <Transition onGlyphClass="1">',
    425     '            <NewState value="0"/>',
    426     '            <MarkIndex value="65535"/>',
    427     '            <CurrentIndex value="65535"/>',
    428     '          </Transition>',
    429     '          <Transition onGlyphClass="2">',
    430     '            <NewState value="0"/>',
    431     '            <MarkIndex value="65535"/>',
    432     '            <CurrentIndex value="65535"/>',
    433     '          </Transition>',
    434     '          <Transition onGlyphClass="3">',
    435     '            <NewState value="0"/>',
    436     '            <MarkIndex value="65535"/>',
    437     '            <CurrentIndex value="65535"/>',
    438     '          </Transition>',
    439     '          <Transition onGlyphClass="4">',
    440     '            <NewState value="0"/>',
    441     '            <MarkIndex value="65535"/>',
    442     '            <CurrentIndex value="0"/>',
    443     '          </Transition>',
    444     '          <Transition onGlyphClass="5">',
    445     '            <NewState value="2"/>',
    446     '            <MarkIndex value="65535"/>',
    447     '            <CurrentIndex value="65535"/>',
    448     '          </Transition>',
    449     '        </State>',
    450     '        <PerGlyphLookup index="0">',
    451     '          <Lookup glyph="A" value="A.swash"/>',
    452     '          <Lookup glyph="B" value="B.swash"/>',
    453     '          <Lookup glyph="X" value="X.swash"/>',
    454     '          <Lookup glyph="Y" value="Y.swash"/>',
    455     '        </PerGlyphLookup>',
    456     '      </StateTable>',
    457     '    </ContextualMorph>',
    458     '  </MorphSubtable>',
    459     '</MorphChain>',
    460 ]
    461 
    462 
    463 # Taken from Example 2: A ligature table in
    464 # https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6morx.html
    465 # as retrieved on 2017-09-11.
    466 #
    467 # Compared to the example table in Apples specification, weve
    468 # made the following changes:
    469 #
    470 # * at offsets 0..35, weve prepended 36 bytes of boilerplate
    471 #   to make the data a structurally valid morx table;
    472 #
    473 # * at offsets 88..91 (offsets 52..55 in Apples document), weve
    474 #   changed the range of the third segment from 23..24 to 26..28.
    475 #   The hexdump values in Apples specification are completely wrong;
    476 #   the values from the comments would work, but they can be encoded
    477 #   more compactly than in the specification example. For round-trip
    478 #   testing, we omit the f glyph, which makes AAT lookup format 2
    479 #   the most compact encoding;
    480 #
    481 # * at offsets 92..93 (offsets 56..57 in Apples document), weve
    482 #   changed the glyph class of the third segment from 5 to 6, which
    483 #   matches the values from the comments to the spec (but not the
    484 #   Apples hexdump).
    485 #
    486 # TODO: Ask Apple to fix Example 2 in the morx specification.
    487 MORX_LIGATURE_DATA = deHexStr(
    488     '0002 0000 '  #  0: Version=2, Reserved=0
    489     '0000 0001 '  #  4: MorphChainCount=1
    490     '0000 0001 '  #  8: DefaultFlags=1
    491     '0000 00DA '  # 12: StructLength=218 (+8=226)
    492     '0000 0000 '  # 16: MorphFeatureCount=0
    493     '0000 0001 '  # 20: MorphSubtableCount=1
    494     '0000 00CA '  # 24: Subtable[0].StructLength=202 (+24=226)
    495     '80 '         # 28: Subtable[0].CoverageFlags=0x80
    496     '00 00 '      # 29: Subtable[0].Reserved=0
    497     '02 '         # 31: Subtable[0].MorphType=2/LigatureMorph
    498     '0000 0001 '  # 32: Subtable[0].SubFeatureFlags=0x1
    499 
    500     # State table header.
    501     '0000 0007 '  # 36: STXHeader.ClassCount=7
    502     '0000 001C '  # 40: STXHeader.ClassTableOffset=28 (+36=64)
    503     '0000 0040 '  # 44: STXHeader.StateArrayOffset=64 (+36=100)
    504     '0000 0078 '  # 48: STXHeader.EntryTableOffset=120 (+36=156)
    505     '0000 0090 '  # 52: STXHeader.LigActionsOffset=144 (+36=180)
    506     '0000 009C '  # 56: STXHeader.LigComponentsOffset=156 (+36=192)
    507     '0000 00AE '  # 60: STXHeader.LigListOffset=174 (+36=210)
    508 
    509     # Glyph class table.
    510     '0002 0006 '       # 64: ClassTable.LookupFormat=2, .UnitSize=6
    511     '0003 000C '       # 68:   .NUnits=3, .SearchRange=12
    512     '0001 0006 '       # 72:   .EntrySelector=1, .RangeShift=6
    513     '0016 0014 0004 '  # 76: GlyphID 20..22 [a..c] -> GlyphClass 4
    514     '0018 0017 0005 '  # 82: GlyphID 23..24 [d..e] -> GlyphClass 5
    515     '001C 001A 0006 '  # 88: GlyphID 26..28 [g..i] -> GlyphClass 6
    516     'FFFF FFFF 0000 '  # 94: <end of lookup>
    517 
    518     # State array.
    519     '0000 0000 0000 0000 0001 0000 0000 '  # 100: State[0][0..6]
    520     '0000 0000 0000 0000 0001 0000 0000 '  # 114: State[1][0..6]
    521     '0000 0000 0000 0000 0001 0002 0000 '  # 128: State[2][0..6]
    522     '0000 0000 0000 0000 0001 0002 0003 '  # 142: State[3][0..6]
    523 
    524     # Entry table.
    525     '0000 0000 '  # 156: Entries[0].NewState=0, .Flags=0
    526     '0000 '       # 160: Entries[0].ActionIndex=<n/a> because no 0x2000 flag
    527     '0002 8000 '  # 162: Entries[1].NewState=2, .Flags=0x8000 (SetComponent)
    528     '0000 '       # 166: Entries[1].ActionIndex=<n/a> because no 0x2000 flag
    529     '0003 8000 '  # 168: Entries[2].NewState=3, .Flags=0x8000 (SetComponent)
    530     '0000 '       # 172: Entries[2].ActionIndex=<n/a> because no 0x2000 flag
    531     '0000 A000 '  # 174: Entries[3].NewState=0, .Flags=0xA000 (SetComponent,Act)
    532     '0000 '       # 178: Entries[3].ActionIndex=0 (start at Action[0])
    533 
    534     # Ligature actions table.
    535     '3FFF FFE7 '  # 180: Action[0].Flags=0, .GlyphIndexDelta=-25
    536     '3FFF FFED '  # 184: Action[1].Flags=0, .GlyphIndexDelta=-19
    537     'BFFF FFF2 '  # 188: Action[2].Flags=<end of list>, .GlyphIndexDelta=-14
    538 
    539     # Ligature component table.
    540     '0000 0001 '  # 192: LigComponent[0]=0, LigComponent[1]=1
    541     '0002 0003 '  # 196: LigComponent[2]=2, LigComponent[3]=3
    542     '0000 0004 '  # 200: LigComponent[4]=0, LigComponent[5]=4
    543     '0000 0008 '  # 204: LigComponent[6]=0, LigComponent[7]=8
    544     '0010      '  # 208: LigComponent[8]=16
    545 
    546     # Ligature list.
    547     '03E8 03E9 '  # 210: LigList[0]=1000, LigList[1]=1001
    548     '03EA 03EB '  # 214: LigList[2]=1002, LigList[3]=1003
    549     '03EC 03ED '  # 218: LigList[4]=1004, LigList[3]=1005
    550     '03EE 03EF '  # 222: LigList[5]=1006, LigList[6]=1007
    551 )   # 226: <end>
    552 assert len(MORX_LIGATURE_DATA) == 226, len(MORX_LIGATURE_DATA)
    553 
    554 
    555 MORX_LIGATURE_XML = [
    556     '<Version value="2"/>',
    557     '<Reserved value="0"/>',
    558     '<!-- MorphChainCount=1 -->',
    559     '<MorphChain index="0">',
    560     '  <DefaultFlags value="0x00000001"/>',
    561     '  <!-- StructLength=218 -->',
    562     '  <!-- MorphFeatureCount=0 -->',
    563     '  <!-- MorphSubtableCount=1 -->',
    564     '  <MorphSubtable index="0">',
    565     '    <!-- StructLength=202 -->',
    566     '    <TextDirection value="Vertical"/>',
    567     '    <ProcessingOrder value="LayoutOrder"/>',
    568     '    <!-- MorphType=2 -->',
    569     '    <SubFeatureFlags value="0x00000001"/>',
    570     '    <LigatureMorph>',
    571     '      <StateTable>',
    572     '        <!-- GlyphClassCount=7 -->',
    573     '        <GlyphClass glyph="a" value="4"/>',
    574     '        <GlyphClass glyph="b" value="4"/>',
    575     '        <GlyphClass glyph="c" value="4"/>',
    576     '        <GlyphClass glyph="d" value="5"/>',
    577     '        <GlyphClass glyph="e" value="5"/>',
    578     '        <GlyphClass glyph="g" value="6"/>',
    579     '        <GlyphClass glyph="h" value="6"/>',
    580     '        <GlyphClass glyph="i" value="6"/>',
    581     '        <State index="0">',
    582     '          <Transition onGlyphClass="0">',
    583     '            <NewState value="0"/>',
    584     '          </Transition>',
    585     '          <Transition onGlyphClass="1">',
    586     '            <NewState value="0"/>',
    587     '          </Transition>',
    588     '          <Transition onGlyphClass="2">',
    589     '            <NewState value="0"/>',
    590     '          </Transition>',
    591     '          <Transition onGlyphClass="3">',
    592     '            <NewState value="0"/>',
    593     '          </Transition>',
    594     '          <Transition onGlyphClass="4">',
    595     '            <NewState value="2"/>',
    596     '            <Flags value="SetComponent"/>',
    597     '          </Transition>',
    598     '          <Transition onGlyphClass="5">',
    599     '            <NewState value="0"/>',
    600     '          </Transition>',
    601     '          <Transition onGlyphClass="6">',
    602     '            <NewState value="0"/>',
    603     '          </Transition>',
    604     '        </State>',
    605     '        <State index="1">',
    606     '          <Transition onGlyphClass="0">',
    607     '            <NewState value="0"/>',
    608     '          </Transition>',
    609     '          <Transition onGlyphClass="1">',
    610     '            <NewState value="0"/>',
    611     '          </Transition>',
    612     '          <Transition onGlyphClass="2">',
    613     '            <NewState value="0"/>',
    614     '          </Transition>',
    615     '          <Transition onGlyphClass="3">',
    616     '            <NewState value="0"/>',
    617     '          </Transition>',
    618     '          <Transition onGlyphClass="4">',
    619     '            <NewState value="2"/>',
    620     '            <Flags value="SetComponent"/>',
    621     '          </Transition>',
    622     '          <Transition onGlyphClass="5">',
    623     '            <NewState value="0"/>',
    624     '          </Transition>',
    625     '          <Transition onGlyphClass="6">',
    626     '            <NewState value="0"/>',
    627     '          </Transition>',
    628     '        </State>',
    629     '        <State index="2">',
    630     '          <Transition onGlyphClass="0">',
    631     '            <NewState value="0"/>',
    632     '          </Transition>',
    633     '          <Transition onGlyphClass="1">',
    634     '            <NewState value="0"/>',
    635     '          </Transition>',
    636     '          <Transition onGlyphClass="2">',
    637     '            <NewState value="0"/>',
    638     '          </Transition>',
    639     '          <Transition onGlyphClass="3">',
    640     '            <NewState value="0"/>',
    641     '          </Transition>',
    642     '          <Transition onGlyphClass="4">',
    643     '            <NewState value="2"/>',
    644     '            <Flags value="SetComponent"/>',
    645     '          </Transition>',
    646     '          <Transition onGlyphClass="5">',
    647     '            <NewState value="3"/>',
    648     '            <Flags value="SetComponent"/>',
    649     '          </Transition>',
    650     '          <Transition onGlyphClass="6">',
    651     '            <NewState value="0"/>',
    652     '          </Transition>',
    653     '        </State>',
    654     '        <State index="3">',
    655     '          <Transition onGlyphClass="0">',
    656     '            <NewState value="0"/>',
    657     '          </Transition>',
    658     '          <Transition onGlyphClass="1">',
    659     '            <NewState value="0"/>',
    660     '          </Transition>',
    661     '          <Transition onGlyphClass="2">',
    662     '            <NewState value="0"/>',
    663     '          </Transition>',
    664     '          <Transition onGlyphClass="3">',
    665     '            <NewState value="0"/>',
    666     '          </Transition>',
    667     '          <Transition onGlyphClass="4">',
    668     '            <NewState value="2"/>',
    669     '            <Flags value="SetComponent"/>',
    670     '          </Transition>',
    671     '          <Transition onGlyphClass="5">',
    672     '            <NewState value="3"/>',
    673     '            <Flags value="SetComponent"/>',
    674     '          </Transition>',
    675     '          <Transition onGlyphClass="6">',
    676     '            <NewState value="0"/>',
    677     '            <Flags value="SetComponent"/>',
    678     '            <Action GlyphIndexDelta="-25"/>',
    679     '            <Action GlyphIndexDelta="-19"/>',
    680     '            <Action GlyphIndexDelta="-14"/>',
    681     '          </Transition>',
    682     '        </State>',
    683     '        <LigComponents>',
    684     '          <LigComponent index="0" value="0"/>',
    685     '          <LigComponent index="1" value="1"/>',
    686     '          <LigComponent index="2" value="2"/>',
    687     '          <LigComponent index="3" value="3"/>',
    688     '          <LigComponent index="4" value="0"/>',
    689     '          <LigComponent index="5" value="4"/>',
    690     '          <LigComponent index="6" value="0"/>',
    691     '          <LigComponent index="7" value="8"/>',
    692     '          <LigComponent index="8" value="16"/>',
    693     '        </LigComponents>',
    694     '        <Ligatures>',
    695     '          <Ligature glyph="adf" index="0"/>',
    696     '          <Ligature glyph="adg" index="1"/>',
    697     '          <Ligature glyph="adh" index="2"/>',
    698     '          <Ligature glyph="adi" index="3"/>',
    699     '          <Ligature glyph="aef" index="4"/>',
    700     '          <Ligature glyph="aeg" index="5"/>',
    701     '          <Ligature glyph="aeh" index="6"/>',
    702     '          <Ligature glyph="aei" index="7"/>',
    703     '        </Ligatures>',
    704     '      </StateTable>',
    705     '    </LigatureMorph>',
    706     '  </MorphSubtable>',
    707     '</MorphChain>',
    708 ]
    709 
    710 
    711 # Taken from the `morx` table of the second font in DevanagariSangamMN.ttc
    712 # on macOS X 10.12.6; manually pruned to just contain the insertion lookup.
    713 MORX_INSERTION_DATA = deHexStr(
    714     '0002 0000 '  #  0: Version=2, Reserved=0
    715     '0000 0001 '  #  4: MorphChainCount=1
    716     '0000 0001 '  #  8: DefaultFlags=1
    717     '0000 00A4 '  # 12: StructLength=164 (+8=172)
    718     '0000 0000 '  # 16: MorphFeatureCount=0
    719     '0000 0001 '  # 20: MorphSubtableCount=1
    720     '0000 0094 '  # 24: Subtable[0].StructLength=148 (+24=172)
    721     '00 '         # 28: Subtable[0].CoverageFlags=0x00
    722     '00 00 '      # 29: Subtable[0].Reserved=0
    723     '05 '         # 31: Subtable[0].MorphType=5/InsertionMorph
    724     '0000 0001 '  # 32: Subtable[0].SubFeatureFlags=0x1
    725     '0000 0006 '  # 36: STXHeader.ClassCount=6
    726     '0000 0014 '  # 40: STXHeader.ClassTableOffset=20 (+36=56)
    727     '0000 004A '  # 44: STXHeader.StateArrayOffset=74 (+36=110)
    728     '0000 006E '  # 48: STXHeader.EntryTableOffset=110 (+36=146)
    729     '0000 0086 '  # 52: STXHeader.InsertionActionOffset=134 (+36=170)
    730      # Glyph class table.
    731     '0002 0006 '       #  56: ClassTable.LookupFormat=2, .UnitSize=6
    732     '0006 0018 '       #  60:   .NUnits=6, .SearchRange=24
    733     '0002 000C '       #  64:   .EntrySelector=2, .RangeShift=12
    734     '00AC 00AC 0005 '  #  68: GlyphID 172..172 -> GlyphClass 5
    735     '01EB 01E6 0005 '  #  74: GlyphID 486..491 -> GlyphClass 5
    736     '01F0 01F0 0004 '  #  80: GlyphID 496..496 -> GlyphClass 4
    737     '01F8 01F6 0004 '  #  88: GlyphID 502..504 -> GlyphClass 4
    738     '01FC 01FA 0004 '  #  92: GlyphID 506..508 -> GlyphClass 4
    739     '0250 0250 0005 '  #  98: GlyphID 592..592 -> GlyphClass 5
    740     'FFFF FFFF 0000 '  # 104: <end of lookup>
    741     # State array.
    742     '0000 0000 0000 0000 0001 0000 '  # 110: State[0][0..5]
    743     '0000 0000 0000 0000 0001 0000 '  # 122: State[1][0..5]
    744     '0000 0000 0001 0000 0001 0002 '  # 134: State[2][0..5]
    745     # Entry table.
    746     '0000 0000 '  # 146: Entries[0].NewState=0, .Flags=0
    747     'FFFF '       # 150: Entries[0].CurrentInsertIndex=<None>
    748     'FFFF '       # 152: Entries[0].MarkedInsertIndex=<None>
    749     '0002 0000 '  # 154: Entries[1].NewState=0, .Flags=0
    750     'FFFF '       # 158: Entries[1].CurrentInsertIndex=<None>
    751     'FFFF '       # 160: Entries[1].MarkedInsertIndex=<None>
    752     '0000 '       # 162: Entries[2].NewState=0
    753     '2820 '       # 164:   .Flags=CurrentIsKashidaLike,CurrentInsertBefore
    754                   #        .CurrentInsertCount=1, .MarkedInsertCount=0
    755     '0000 '       # 166: Entries[1].CurrentInsertIndex=0
    756     'FFFF '       # 168: Entries[1].MarkedInsertIndex=<None>
    757     # Insertion action table.
    758     '022F'        # 170: InsertionActionTable[0]=GlyphID 559
    759 )   # 172: <end>
    760 assert len(MORX_INSERTION_DATA) == 172, len(MORX_INSERTION_DATA)
    761 
    762 
    763 MORX_INSERTION_XML = [
    764     '<Version value="2"/>',
    765     '<Reserved value="0"/>',
    766     '<!-- MorphChainCount=1 -->',
    767     '<MorphChain index="0">',
    768     '  <DefaultFlags value="0x00000001"/>',
    769     '  <!-- StructLength=164 -->',
    770     '  <!-- MorphFeatureCount=0 -->',
    771     '  <!-- MorphSubtableCount=1 -->',
    772     '  <MorphSubtable index="0">',
    773     '    <!-- StructLength=148 -->',
    774     '    <TextDirection value="Horizontal"/>',
    775     '    <ProcessingOrder value="LayoutOrder"/>',
    776     '    <!-- MorphType=5 -->',
    777     '    <SubFeatureFlags value="0x00000001"/>',
    778     '    <InsertionMorph>',
    779     '      <StateTable>',
    780     '        <!-- GlyphClassCount=6 -->',
    781     '        <GlyphClass glyph="g.172" value="5"/>',
    782     '        <GlyphClass glyph="g.486" value="5"/>',
    783     '        <GlyphClass glyph="g.487" value="5"/>',
    784     '        <GlyphClass glyph="g.488" value="5"/>',
    785     '        <GlyphClass glyph="g.489" value="5"/>',
    786     '        <GlyphClass glyph="g.490" value="5"/>',
    787     '        <GlyphClass glyph="g.491" value="5"/>',
    788     '        <GlyphClass glyph="g.496" value="4"/>',
    789     '        <GlyphClass glyph="g.502" value="4"/>',
    790     '        <GlyphClass glyph="g.503" value="4"/>',
    791     '        <GlyphClass glyph="g.504" value="4"/>',
    792     '        <GlyphClass glyph="g.506" value="4"/>',
    793     '        <GlyphClass glyph="g.507" value="4"/>',
    794     '        <GlyphClass glyph="g.508" value="4"/>',
    795     '        <GlyphClass glyph="g.592" value="5"/>',
    796     '        <State index="0">',
    797     '          <Transition onGlyphClass="0">',
    798     '            <NewState value="0"/>',
    799     '          </Transition>',
    800     '          <Transition onGlyphClass="1">',
    801     '            <NewState value="0"/>',
    802     '          </Transition>',
    803     '          <Transition onGlyphClass="2">',
    804     '            <NewState value="0"/>',
    805     '          </Transition>',
    806     '          <Transition onGlyphClass="3">',
    807     '            <NewState value="0"/>',
    808     '          </Transition>',
    809     '          <Transition onGlyphClass="4">',
    810     '            <NewState value="2"/>',
    811     '          </Transition>',
    812     '          <Transition onGlyphClass="5">',
    813     '            <NewState value="0"/>',
    814     '          </Transition>',
    815     '        </State>',
    816     '        <State index="1">',
    817     '          <Transition onGlyphClass="0">',
    818     '            <NewState value="0"/>',
    819     '          </Transition>',
    820     '          <Transition onGlyphClass="1">',
    821     '            <NewState value="0"/>',
    822     '          </Transition>',
    823     '          <Transition onGlyphClass="2">',
    824     '            <NewState value="0"/>',
    825     '          </Transition>',
    826     '          <Transition onGlyphClass="3">',
    827     '            <NewState value="0"/>',
    828     '          </Transition>',
    829     '          <Transition onGlyphClass="4">',
    830     '            <NewState value="2"/>',
    831     '          </Transition>',
    832     '          <Transition onGlyphClass="5">',
    833     '            <NewState value="0"/>',
    834     '          </Transition>',
    835     '        </State>',
    836     '        <State index="2">',
    837     '          <Transition onGlyphClass="0">',
    838     '            <NewState value="0"/>',
    839     '          </Transition>',
    840     '          <Transition onGlyphClass="1">',
    841     '            <NewState value="0"/>',
    842     '          </Transition>',
    843     '          <Transition onGlyphClass="2">',
    844     '            <NewState value="2"/>',
    845     '          </Transition>',
    846     '          <Transition onGlyphClass="3">',
    847     '            <NewState value="0"/>',
    848     '          </Transition>',
    849     '          <Transition onGlyphClass="4">',
    850     '            <NewState value="2"/>',
    851     '          </Transition>',
    852     '          <Transition onGlyphClass="5">',
    853     '            <NewState value="0"/>',
    854     '            <Flags value="CurrentIsKashidaLike,CurrentInsertBefore"/>',
    855     '            <CurrentInsertionAction glyph="g.559"/>',
    856     '          </Transition>',
    857     '        </State>',
    858     '      </StateTable>',
    859     '    </InsertionMorph>',
    860     '  </MorphSubtable>',
    861     '</MorphChain>',
    862 ]
    863 
    864 
    865 class MORXNoncontextualGlyphSubstitutionTest(unittest.TestCase):
    866 
    867     @classmethod
    868     def setUpClass(cls):
    869         cls.maxDiff = None
    870         glyphs = ['.notdef'] + ['g.%d' % i for i in range (1, 140)]
    871         glyphs[11], glyphs[13] = 'parenleft', 'parenright'
    872         glyphs[135], glyphs[136] = 'parenleft.vertical', 'parenright.vertical'
    873         cls.font = FakeFont(glyphs)
    874 
    875     def test_decompile_toXML(self):
    876         table = newTable('morx')
    877         table.decompile(MORX_NONCONTEXTUAL_DATA, self.font)
    878         self.assertEqual(getXML(table.toXML), MORX_NONCONTEXTUAL_XML)
    879 
    880     def test_compile_fromXML(self):
    881         table = newTable('morx')
    882         for name, attrs, content in parseXML(MORX_NONCONTEXTUAL_XML):
    883             table.fromXML(name, attrs, content, font=self.font)
    884         self.assertEqual(hexStr(table.compile(self.font)),
    885                          hexStr(MORX_NONCONTEXTUAL_DATA))
    886 
    887 
    888 class MORXRearrangementTest(unittest.TestCase):
    889 
    890     @classmethod
    891     def setUpClass(cls):
    892         cls.maxDiff = None
    893         cls.font = FakeFont(['.nodef', 'A', 'B', 'C'])
    894 
    895     def test_decompile_toXML(self):
    896         table = newTable('morx')
    897         table.decompile(MORX_REARRANGEMENT_DATA, self.font)
    898         self.assertEqual(getXML(table.toXML), MORX_REARRANGEMENT_XML)
    899 
    900     def test_compile_fromXML(self):
    901         table = newTable('morx')
    902         for name, attrs, content in parseXML(MORX_REARRANGEMENT_XML):
    903             table.fromXML(name, attrs, content, font=self.font)
    904         self.assertEqual(hexStr(table.compile(self.font)),
    905                          hexStr(MORX_REARRANGEMENT_DATA))
    906 
    907 
    908 class MORXContextualSubstitutionTest(unittest.TestCase):
    909 
    910     @classmethod
    911     def setUpClass(cls):
    912         cls.maxDiff = None
    913         g = ['.notdef'] + ['g.%d' % i for i in range (1, 910)]
    914         g[80] = 'C'
    915         g[50], g[52], g[201], g[202] = 'A', 'B', 'X', 'Y'
    916         g[600], g[601], g[602], g[900] = (
    917             'A.swash', 'B.swash', 'X.swash', 'Y.swash')
    918         cls.font = FakeFont(g)
    919 
    920     def test_decompile_toXML(self):
    921         table = newTable('morx')
    922         table.decompile(MORX_CONTEXTUAL_DATA, self.font)
    923         self.assertEqual(getXML(table.toXML), MORX_CONTEXTUAL_XML)
    924 
    925     def test_compile_fromXML(self):
    926         table = newTable('morx')
    927         for name, attrs, content in parseXML(MORX_CONTEXTUAL_XML):
    928             table.fromXML(name, attrs, content, font=self.font)
    929         self.assertEqual(hexStr(table.compile(self.font)),
    930                          hexStr(MORX_CONTEXTUAL_DATA))
    931 
    932 
    933 class MORXLigatureSubstitutionTest(unittest.TestCase):
    934 
    935     @classmethod
    936     def setUpClass(cls):
    937         cls.maxDiff = None
    938         g = ['.notdef'] + ['g.%d' % i for i in range (1, 1515)]
    939         g[20:29] = 'a b c d e f g h i'.split()
    940         g[1000:1008] = 'adf adg adh adi aef aeg aeh aei'.split()
    941         g[1008:1016] = 'bdf bdg bdh bdi bef beg beh bei'.split()
    942         g[1500:1507] = 'cdf cdg cdh cdi cef ceg ceh'.split()
    943         g[1511] = 'cei'
    944         cls.font = FakeFont(g)
    945 
    946     def test_decompile_toXML(self):
    947         table = newTable('morx')
    948         table.decompile(MORX_LIGATURE_DATA, self.font)
    949         self.assertEqual(getXML(table.toXML), MORX_LIGATURE_XML)
    950 
    951     def test_compile_fromXML(self):
    952         table = newTable('morx')
    953         for name, attrs, content in parseXML(MORX_LIGATURE_XML):
    954             table.fromXML(name, attrs, content, font=self.font)
    955         self.assertEqual(hexStr(table.compile(self.font)),
    956                          hexStr(MORX_LIGATURE_DATA))
    957 
    958 
    959 class MORXGlyphInsertionTest(unittest.TestCase):
    960 
    961     @classmethod
    962     def setUpClass(cls):
    963         cls.maxDiff = None
    964         cls.font = FakeFont(['.notdef'] + ['g.%d' % i for i in range (1, 910)])
    965 
    966     def test_decompile_toXML(self):
    967         table = newTable('morx')
    968         table.decompile(MORX_INSERTION_DATA, self.font)
    969         self.assertEqual(getXML(table.toXML), MORX_INSERTION_XML)
    970 
    971     def test_compile_fromXML(self):
    972         table = newTable('morx')
    973         for name, attrs, content in parseXML(MORX_INSERTION_XML):
    974             table.fromXML(name, attrs, content, font=self.font)
    975         self.assertEqual(hexStr(table.compile(self.font)),
    976                          hexStr(MORX_INSERTION_DATA))
    977 
    978 
    979 class MORXCoverageFlagsTest(unittest.TestCase):
    980 
    981     @classmethod
    982     def setUpClass(cls):
    983         cls.maxDiff = None
    984         cls.font = FakeFont(['.notdef', 'A', 'B', 'C'])
    985 
    986     def checkFlags(self, flags, textDirection, processingOrder,
    987                    checkCompile=True):
    988         data = bytesjoin([
    989             MORX_REARRANGEMENT_DATA[:28],
    990             bytechr(flags << 4),
    991             MORX_REARRANGEMENT_DATA[29:]])
    992         xml = []
    993         for line in MORX_REARRANGEMENT_XML:
    994             if line.startswith('    <TextDirection '):
    995                 line = '    <TextDirection value="%s"/>' % textDirection
    996             elif line.startswith('    <ProcessingOrder '):
    997                 line = '    <ProcessingOrder value="%s"/>' % processingOrder
    998             xml.append(line)
    999         table1 = newTable('morx')
   1000         table1.decompile(data, self.font)
   1001         self.assertEqual(getXML(table1.toXML), xml)
   1002         if checkCompile:
   1003             table2 = newTable('morx')
   1004             for name, attrs, content in parseXML(xml):
   1005                 table2.fromXML(name, attrs, content, font=self.font)
   1006             self.assertEqual(hexStr(table2.compile(self.font)), hexStr(data))
   1007 
   1008     def test_CoverageFlags(self):
   1009         self.checkFlags(0x0, "Horizontal", "LayoutOrder")
   1010         self.checkFlags(0x1, "Horizontal", "LogicalOrder")
   1011         self.checkFlags(0x2, "Any", "LayoutOrder")
   1012         self.checkFlags(0x3, "Any", "LogicalOrder")
   1013         self.checkFlags(0x4, "Horizontal", "ReversedLayoutOrder")
   1014         self.checkFlags(0x5, "Horizontal", "ReversedLogicalOrder")
   1015         self.checkFlags(0x6, "Any", "ReversedLayoutOrder")
   1016         self.checkFlags(0x7, "Any", "ReversedLogicalOrder")
   1017         self.checkFlags(0x8, "Vertical", "LayoutOrder")
   1018         self.checkFlags(0x9, "Vertical", "LogicalOrder")
   1019         # We do not always check the compilation to binary data:
   1020         # some flag combinations do not make sense to emit in binary.
   1021         # Specifically, if bit 28 (TextDirection=Any) is set in
   1022         # CoverageFlags, bit 30 (TextDirection=Vertical) is to be
   1023         # ignored according to the 'morx' specification. We still want
   1024         # to test the _decoding_ of 'morx' subtables whose CoverageFlags
   1025         # have both bits 28 and 30 set, since this is a valid flag
   1026         # combination with defined semantics.  However, our encoder
   1027         # does not set TextDirection=Vertical when TextDirection=Any.
   1028         self.checkFlags(0xA, "Any", "LayoutOrder", checkCompile=False)
   1029         self.checkFlags(0xB, "Any", "LogicalOrder", checkCompile=False)
   1030         self.checkFlags(0xC, "Vertical", "ReversedLayoutOrder")
   1031         self.checkFlags(0xD, "Vertical", "ReversedLogicalOrder")
   1032         self.checkFlags(0xE, "Any", "ReversedLayoutOrder", checkCompile=False)
   1033         self.checkFlags(0xF, "Any", "ReversedLogicalOrder", checkCompile=False)
   1034 
   1035     def test_ReservedCoverageFlags(self):
   1036         # 8A BC DE = TextDirection=Vertical, Reserved=0xABCDE
   1037         # Note that the lower 4 bits of the first byte are already
   1038         # part of the Reserved value. We test the full round-trip
   1039         # to encoding and decoding is quite hairy.
   1040         data = bytesjoin([
   1041             MORX_REARRANGEMENT_DATA[:28],
   1042             bytechr(0x8A), bytechr(0xBC), bytechr(0xDE),
   1043             MORX_REARRANGEMENT_DATA[31:]])
   1044         table = newTable('morx')
   1045         table.decompile(data, self.font)
   1046         subtable = table.table.MorphChain[0].MorphSubtable[0]
   1047         self.assertEqual(subtable.Reserved, 0xABCDE)
   1048         xml = getXML(table.toXML)
   1049         self.assertIn('    <Reserved value="0xabcde"/>', xml)
   1050         table2 = newTable('morx')
   1051         for name, attrs, content in parseXML(xml):
   1052             table2.fromXML(name, attrs, content, font=self.font)
   1053         self.assertEqual(hexStr(table2.compile(self.font)[28:31]), "8abcde")
   1054 
   1055 
   1056 class UnsupportedMorxLookupTest(unittest.TestCase):
   1057     def __init__(self, methodName):
   1058         unittest.TestCase.__init__(self, methodName)
   1059         # Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
   1060         # and fires deprecation warnings if a program uses the old name.
   1061         if not hasattr(self, "assertRaisesRegex"):
   1062             self.assertRaisesRegex = self.assertRaisesRegexp
   1063 
   1064     def test_unsupportedLookupType(self):
   1065         data = bytesjoin([
   1066             MORX_NONCONTEXTUAL_DATA[:67],
   1067             bytechr(66),
   1068             MORX_NONCONTEXTUAL_DATA[69:]])
   1069         with self.assertRaisesRegex(AssertionError,
   1070                                     r"unsupported 'morx' lookup type 66"):
   1071             morx = newTable('morx')
   1072             morx.decompile(data, FakeFont(['.notdef']))
   1073 
   1074 
   1075 if __name__ == '__main__':
   1076     import sys
   1077     sys.exit(unittest.main())
   1078