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