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