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