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