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