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