1# coding: utf-8 2"""fontTools.ttLib.tables.otTables -- A collection of classes representing the various 3OpenType subtables. 4 5Most are constructed upon import from data in otData.py, all are populated with 6converter objects from otConverters.py. 7""" 8from enum import IntEnum 9import itertools 10from collections import namedtuple 11from fontTools.misc.py23 import bytesjoin 12from fontTools.misc.roundTools import otRound 13from fontTools.misc.textTools import pad, safeEval 14from .otBase import ( 15 BaseTable, FormatSwitchingBaseTable, ValueRecord, CountReference, 16 getFormatSwitchingBaseTableClass, 17) 18from fontTools.feaLib.lookupDebugInfo import LookupDebugInfo, LOOKUP_DEBUG_INFO_KEY 19import logging 20import struct 21 22 23log = logging.getLogger(__name__) 24 25 26class AATStateTable(object): 27 def __init__(self): 28 self.GlyphClasses = {} # GlyphID --> GlyphClass 29 self.States = [] # List of AATState, indexed by state number 30 self.PerGlyphLookups = [] # [{GlyphID:GlyphID}, ...] 31 32 33class AATState(object): 34 def __init__(self): 35 self.Transitions = {} # GlyphClass --> AATAction 36 37 38class AATAction(object): 39 _FLAGS = None 40 41 @staticmethod 42 def compileActions(font, states): 43 return (None, None) 44 45 def _writeFlagsToXML(self, xmlWriter): 46 flags = [f for f in self._FLAGS if self.__dict__[f]] 47 if flags: 48 xmlWriter.simpletag("Flags", value=",".join(flags)) 49 xmlWriter.newline() 50 if self.ReservedFlags != 0: 51 xmlWriter.simpletag( 52 "ReservedFlags", 53 value='0x%04X' % self.ReservedFlags) 54 xmlWriter.newline() 55 56 def _setFlag(self, flag): 57 assert flag in self._FLAGS, "unsupported flag %s" % flag 58 self.__dict__[flag] = True 59 60 61class RearrangementMorphAction(AATAction): 62 staticSize = 4 63 actionHeaderSize = 0 64 _FLAGS = ["MarkFirst", "DontAdvance", "MarkLast"] 65 66 _VERBS = { 67 0: "no change", 68 1: "Ax ⇒ xA", 69 2: "xD ⇒ Dx", 70 3: "AxD ⇒ DxA", 71 4: "ABx ⇒ xAB", 72 5: "ABx ⇒ xBA", 73 6: "xCD ⇒ CDx", 74 7: "xCD ⇒ DCx", 75 8: "AxCD ⇒ CDxA", 76 9: "AxCD ⇒ DCxA", 77 10: "ABxD ⇒ DxAB", 78 11: "ABxD ⇒ DxBA", 79 12: "ABxCD ⇒ CDxAB", 80 13: "ABxCD ⇒ CDxBA", 81 14: "ABxCD ⇒ DCxAB", 82 15: "ABxCD ⇒ DCxBA", 83 } 84 85 def __init__(self): 86 self.NewState = 0 87 self.Verb = 0 88 self.MarkFirst = False 89 self.DontAdvance = False 90 self.MarkLast = False 91 self.ReservedFlags = 0 92 93 def compile(self, writer, font, actionIndex): 94 assert actionIndex is None 95 writer.writeUShort(self.NewState) 96 assert self.Verb >= 0 and self.Verb <= 15, self.Verb 97 flags = self.Verb | self.ReservedFlags 98 if self.MarkFirst: flags |= 0x8000 99 if self.DontAdvance: flags |= 0x4000 100 if self.MarkLast: flags |= 0x2000 101 writer.writeUShort(flags) 102 103 def decompile(self, reader, font, actionReader): 104 assert actionReader is None 105 self.NewState = reader.readUShort() 106 flags = reader.readUShort() 107 self.Verb = flags & 0xF 108 self.MarkFirst = bool(flags & 0x8000) 109 self.DontAdvance = bool(flags & 0x4000) 110 self.MarkLast = bool(flags & 0x2000) 111 self.ReservedFlags = flags & 0x1FF0 112 113 def toXML(self, xmlWriter, font, attrs, name): 114 xmlWriter.begintag(name, **attrs) 115 xmlWriter.newline() 116 xmlWriter.simpletag("NewState", value=self.NewState) 117 xmlWriter.newline() 118 self._writeFlagsToXML(xmlWriter) 119 xmlWriter.simpletag("Verb", value=self.Verb) 120 verbComment = self._VERBS.get(self.Verb) 121 if verbComment is not None: 122 xmlWriter.comment(verbComment) 123 xmlWriter.newline() 124 xmlWriter.endtag(name) 125 xmlWriter.newline() 126 127 def fromXML(self, name, attrs, content, font): 128 self.NewState = self.Verb = self.ReservedFlags = 0 129 self.MarkFirst = self.DontAdvance = self.MarkLast = False 130 content = [t for t in content if isinstance(t, tuple)] 131 for eltName, eltAttrs, eltContent in content: 132 if eltName == "NewState": 133 self.NewState = safeEval(eltAttrs["value"]) 134 elif eltName == "Verb": 135 self.Verb = safeEval(eltAttrs["value"]) 136 elif eltName == "ReservedFlags": 137 self.ReservedFlags = safeEval(eltAttrs["value"]) 138 elif eltName == "Flags": 139 for flag in eltAttrs["value"].split(","): 140 self._setFlag(flag.strip()) 141 142 143class ContextualMorphAction(AATAction): 144 staticSize = 8 145 actionHeaderSize = 0 146 _FLAGS = ["SetMark", "DontAdvance"] 147 148 def __init__(self): 149 self.NewState = 0 150 self.SetMark, self.DontAdvance = False, False 151 self.ReservedFlags = 0 152 self.MarkIndex, self.CurrentIndex = 0xFFFF, 0xFFFF 153 154 def compile(self, writer, font, actionIndex): 155 assert actionIndex is None 156 writer.writeUShort(self.NewState) 157 flags = self.ReservedFlags 158 if self.SetMark: flags |= 0x8000 159 if self.DontAdvance: flags |= 0x4000 160 writer.writeUShort(flags) 161 writer.writeUShort(self.MarkIndex) 162 writer.writeUShort(self.CurrentIndex) 163 164 def decompile(self, reader, font, actionReader): 165 assert actionReader is None 166 self.NewState = reader.readUShort() 167 flags = reader.readUShort() 168 self.SetMark = bool(flags & 0x8000) 169 self.DontAdvance = bool(flags & 0x4000) 170 self.ReservedFlags = flags & 0x3FFF 171 self.MarkIndex = reader.readUShort() 172 self.CurrentIndex = reader.readUShort() 173 174 def toXML(self, xmlWriter, font, attrs, name): 175 xmlWriter.begintag(name, **attrs) 176 xmlWriter.newline() 177 xmlWriter.simpletag("NewState", value=self.NewState) 178 xmlWriter.newline() 179 self._writeFlagsToXML(xmlWriter) 180 xmlWriter.simpletag("MarkIndex", value=self.MarkIndex) 181 xmlWriter.newline() 182 xmlWriter.simpletag("CurrentIndex", 183 value=self.CurrentIndex) 184 xmlWriter.newline() 185 xmlWriter.endtag(name) 186 xmlWriter.newline() 187 188 def fromXML(self, name, attrs, content, font): 189 self.NewState = self.ReservedFlags = 0 190 self.SetMark = self.DontAdvance = False 191 self.MarkIndex, self.CurrentIndex = 0xFFFF, 0xFFFF 192 content = [t for t in content if isinstance(t, tuple)] 193 for eltName, eltAttrs, eltContent in content: 194 if eltName == "NewState": 195 self.NewState = safeEval(eltAttrs["value"]) 196 elif eltName == "Flags": 197 for flag in eltAttrs["value"].split(","): 198 self._setFlag(flag.strip()) 199 elif eltName == "ReservedFlags": 200 self.ReservedFlags = safeEval(eltAttrs["value"]) 201 elif eltName == "MarkIndex": 202 self.MarkIndex = safeEval(eltAttrs["value"]) 203 elif eltName == "CurrentIndex": 204 self.CurrentIndex = safeEval(eltAttrs["value"]) 205 206 207class LigAction(object): 208 def __init__(self): 209 self.Store = False 210 # GlyphIndexDelta is a (possibly negative) delta that gets 211 # added to the glyph ID at the top of the AAT runtime 212 # execution stack. It is *not* a byte offset into the 213 # morx table. The result of the addition, which is performed 214 # at run time by the shaping engine, is an index into 215 # the ligature components table. See 'morx' specification. 216 # In the AAT specification, this field is called Offset; 217 # but its meaning is quite different from other offsets 218 # in either AAT or OpenType, so we use a different name. 219 self.GlyphIndexDelta = 0 220 221 222class LigatureMorphAction(AATAction): 223 staticSize = 6 224 225 # 4 bytes for each of {action,ligComponents,ligatures}Offset 226 actionHeaderSize = 12 227 228 _FLAGS = ["SetComponent", "DontAdvance"] 229 230 def __init__(self): 231 self.NewState = 0 232 self.SetComponent, self.DontAdvance = False, False 233 self.ReservedFlags = 0 234 self.Actions = [] 235 236 def compile(self, writer, font, actionIndex): 237 assert actionIndex is not None 238 writer.writeUShort(self.NewState) 239 flags = self.ReservedFlags 240 if self.SetComponent: flags |= 0x8000 241 if self.DontAdvance: flags |= 0x4000 242 if len(self.Actions) > 0: flags |= 0x2000 243 writer.writeUShort(flags) 244 if len(self.Actions) > 0: 245 actions = self.compileLigActions() 246 writer.writeUShort(actionIndex[actions]) 247 else: 248 writer.writeUShort(0) 249 250 def decompile(self, reader, font, actionReader): 251 assert actionReader is not None 252 self.NewState = reader.readUShort() 253 flags = reader.readUShort() 254 self.SetComponent = bool(flags & 0x8000) 255 self.DontAdvance = bool(flags & 0x4000) 256 performAction = bool(flags & 0x2000) 257 # As of 2017-09-12, the 'morx' specification says that 258 # the reserved bitmask in ligature subtables is 0x3FFF. 259 # However, the specification also defines a flag 0x2000, 260 # so the reserved value should actually be 0x1FFF. 261 # TODO: Report this specification bug to Apple. 262 self.ReservedFlags = flags & 0x1FFF 263 actionIndex = reader.readUShort() 264 if performAction: 265 self.Actions = self._decompileLigActions( 266 actionReader, actionIndex) 267 else: 268 self.Actions = [] 269 270 @staticmethod 271 def compileActions(font, states): 272 result, actions, actionIndex = b"", set(), {} 273 for state in states: 274 for _glyphClass, trans in state.Transitions.items(): 275 actions.add(trans.compileLigActions()) 276 # Sort the compiled actions in decreasing order of 277 # length, so that the longer sequence come before the 278 # shorter ones. For each compiled action ABCD, its 279 # suffixes BCD, CD, and D do not be encoded separately 280 # (in case they occur); instead, we can just store an 281 # index that points into the middle of the longer 282 # sequence. Every compiled AAT ligature sequence is 283 # terminated with an end-of-sequence flag, which can 284 # only be set on the last element of the sequence. 285 # Therefore, it is sufficient to consider just the 286 # suffixes. 287 for a in sorted(actions, key=lambda x:(-len(x), x)): 288 if a not in actionIndex: 289 for i in range(0, len(a), 4): 290 suffix = a[i:] 291 suffixIndex = (len(result) + i) // 4 292 actionIndex.setdefault( 293 suffix, suffixIndex) 294 result += a 295 result = pad(result, 4) 296 return (result, actionIndex) 297 298 def compileLigActions(self): 299 result = [] 300 for i, action in enumerate(self.Actions): 301 last = (i == len(self.Actions) - 1) 302 value = action.GlyphIndexDelta & 0x3FFFFFFF 303 value |= 0x80000000 if last else 0 304 value |= 0x40000000 if action.Store else 0 305 result.append(struct.pack(">L", value)) 306 return bytesjoin(result) 307 308 def _decompileLigActions(self, actionReader, actionIndex): 309 actions = [] 310 last = False 311 reader = actionReader.getSubReader( 312 actionReader.pos + actionIndex * 4) 313 while not last: 314 value = reader.readULong() 315 last = bool(value & 0x80000000) 316 action = LigAction() 317 actions.append(action) 318 action.Store = bool(value & 0x40000000) 319 delta = value & 0x3FFFFFFF 320 if delta >= 0x20000000: # sign-extend 30-bit value 321 delta = -0x40000000 + delta 322 action.GlyphIndexDelta = delta 323 return actions 324 325 def fromXML(self, name, attrs, content, font): 326 self.NewState = self.ReservedFlags = 0 327 self.SetComponent = self.DontAdvance = False 328 self.ReservedFlags = 0 329 self.Actions = [] 330 content = [t for t in content if isinstance(t, tuple)] 331 for eltName, eltAttrs, eltContent in content: 332 if eltName == "NewState": 333 self.NewState = safeEval(eltAttrs["value"]) 334 elif eltName == "Flags": 335 for flag in eltAttrs["value"].split(","): 336 self._setFlag(flag.strip()) 337 elif eltName == "ReservedFlags": 338 self.ReservedFlags = safeEval(eltAttrs["value"]) 339 elif eltName == "Action": 340 action = LigAction() 341 flags = eltAttrs.get("Flags", "").split(",") 342 flags = [f.strip() for f in flags] 343 action.Store = "Store" in flags 344 action.GlyphIndexDelta = safeEval( 345 eltAttrs["GlyphIndexDelta"]) 346 self.Actions.append(action) 347 348 def toXML(self, xmlWriter, font, attrs, name): 349 xmlWriter.begintag(name, **attrs) 350 xmlWriter.newline() 351 xmlWriter.simpletag("NewState", value=self.NewState) 352 xmlWriter.newline() 353 self._writeFlagsToXML(xmlWriter) 354 for action in self.Actions: 355 attribs = [("GlyphIndexDelta", action.GlyphIndexDelta)] 356 if action.Store: 357 attribs.append(("Flags", "Store")) 358 xmlWriter.simpletag("Action", attribs) 359 xmlWriter.newline() 360 xmlWriter.endtag(name) 361 xmlWriter.newline() 362 363 364class InsertionMorphAction(AATAction): 365 staticSize = 8 366 actionHeaderSize = 4 # 4 bytes for actionOffset 367 _FLAGS = ["SetMark", "DontAdvance", 368 "CurrentIsKashidaLike", "MarkedIsKashidaLike", 369 "CurrentInsertBefore", "MarkedInsertBefore"] 370 371 def __init__(self): 372 self.NewState = 0 373 for flag in self._FLAGS: 374 setattr(self, flag, False) 375 self.ReservedFlags = 0 376 self.CurrentInsertionAction, self.MarkedInsertionAction = [], [] 377 378 def compile(self, writer, font, actionIndex): 379 assert actionIndex is not None 380 writer.writeUShort(self.NewState) 381 flags = self.ReservedFlags 382 if self.SetMark: flags |= 0x8000 383 if self.DontAdvance: flags |= 0x4000 384 if self.CurrentIsKashidaLike: flags |= 0x2000 385 if self.MarkedIsKashidaLike: flags |= 0x1000 386 if self.CurrentInsertBefore: flags |= 0x0800 387 if self.MarkedInsertBefore: flags |= 0x0400 388 flags |= len(self.CurrentInsertionAction) << 5 389 flags |= len(self.MarkedInsertionAction) 390 writer.writeUShort(flags) 391 if len(self.CurrentInsertionAction) > 0: 392 currentIndex = actionIndex[ 393 tuple(self.CurrentInsertionAction)] 394 else: 395 currentIndex = 0xFFFF 396 writer.writeUShort(currentIndex) 397 if len(self.MarkedInsertionAction) > 0: 398 markedIndex = actionIndex[ 399 tuple(self.MarkedInsertionAction)] 400 else: 401 markedIndex = 0xFFFF 402 writer.writeUShort(markedIndex) 403 404 def decompile(self, reader, font, actionReader): 405 assert actionReader is not None 406 self.NewState = reader.readUShort() 407 flags = reader.readUShort() 408 self.SetMark = bool(flags & 0x8000) 409 self.DontAdvance = bool(flags & 0x4000) 410 self.CurrentIsKashidaLike = bool(flags & 0x2000) 411 self.MarkedIsKashidaLike = bool(flags & 0x1000) 412 self.CurrentInsertBefore = bool(flags & 0x0800) 413 self.MarkedInsertBefore = bool(flags & 0x0400) 414 self.CurrentInsertionAction = self._decompileInsertionAction( 415 actionReader, font, 416 index=reader.readUShort(), 417 count=((flags & 0x03E0) >> 5)) 418 self.MarkedInsertionAction = self._decompileInsertionAction( 419 actionReader, font, 420 index=reader.readUShort(), 421 count=(flags & 0x001F)) 422 423 def _decompileInsertionAction(self, actionReader, font, index, count): 424 if index == 0xFFFF or count == 0: 425 return [] 426 reader = actionReader.getSubReader( 427 actionReader.pos + index * 2) 428 return [font.getGlyphName(glyphID) 429 for glyphID in reader.readUShortArray(count)] 430 431 def toXML(self, xmlWriter, font, attrs, name): 432 xmlWriter.begintag(name, **attrs) 433 xmlWriter.newline() 434 xmlWriter.simpletag("NewState", value=self.NewState) 435 xmlWriter.newline() 436 self._writeFlagsToXML(xmlWriter) 437 for g in self.CurrentInsertionAction: 438 xmlWriter.simpletag("CurrentInsertionAction", glyph=g) 439 xmlWriter.newline() 440 for g in self.MarkedInsertionAction: 441 xmlWriter.simpletag("MarkedInsertionAction", glyph=g) 442 xmlWriter.newline() 443 xmlWriter.endtag(name) 444 xmlWriter.newline() 445 446 def fromXML(self, name, attrs, content, font): 447 self.__init__() 448 content = [t for t in content if isinstance(t, tuple)] 449 for eltName, eltAttrs, eltContent in content: 450 if eltName == "NewState": 451 self.NewState = safeEval(eltAttrs["value"]) 452 elif eltName == "Flags": 453 for flag in eltAttrs["value"].split(","): 454 self._setFlag(flag.strip()) 455 elif eltName == "CurrentInsertionAction": 456 self.CurrentInsertionAction.append( 457 eltAttrs["glyph"]) 458 elif eltName == "MarkedInsertionAction": 459 self.MarkedInsertionAction.append( 460 eltAttrs["glyph"]) 461 else: 462 assert False, eltName 463 464 @staticmethod 465 def compileActions(font, states): 466 actions, actionIndex, result = set(), {}, b"" 467 for state in states: 468 for _glyphClass, trans in state.Transitions.items(): 469 if trans.CurrentInsertionAction is not None: 470 actions.add(tuple(trans.CurrentInsertionAction)) 471 if trans.MarkedInsertionAction is not None: 472 actions.add(tuple(trans.MarkedInsertionAction)) 473 # Sort the compiled actions in decreasing order of 474 # length, so that the longer sequence come before the 475 # shorter ones. 476 for action in sorted(actions, key=lambda x:(-len(x), x)): 477 # We insert all sub-sequences of the action glyph sequence 478 # into actionIndex. For example, if one action triggers on 479 # glyph sequence [A, B, C, D, E] and another action triggers 480 # on [C, D], we return result=[A, B, C, D, E] (as list of 481 # encoded glyph IDs), and actionIndex={('A','B','C','D','E'): 0, 482 # ('C','D'): 2}. 483 if action in actionIndex: 484 continue 485 for start in range(0, len(action)): 486 startIndex = (len(result) // 2) + start 487 for limit in range(start, len(action)): 488 glyphs = action[start : limit + 1] 489 actionIndex.setdefault(glyphs, startIndex) 490 for glyph in action: 491 glyphID = font.getGlyphID(glyph) 492 result += struct.pack(">H", glyphID) 493 return result, actionIndex 494 495 496class FeatureParams(BaseTable): 497 498 def compile(self, writer, font): 499 assert featureParamTypes.get(writer['FeatureTag']) == self.__class__, "Wrong FeatureParams type for feature '%s': %s" % (writer['FeatureTag'], self.__class__.__name__) 500 BaseTable.compile(self, writer, font) 501 502 def toXML(self, xmlWriter, font, attrs=None, name=None): 503 BaseTable.toXML(self, xmlWriter, font, attrs, name=self.__class__.__name__) 504 505class FeatureParamsSize(FeatureParams): 506 pass 507 508class FeatureParamsStylisticSet(FeatureParams): 509 pass 510 511class FeatureParamsCharacterVariants(FeatureParams): 512 pass 513 514class Coverage(FormatSwitchingBaseTable): 515 516 # manual implementation to get rid of glyphID dependencies 517 518 def populateDefaults(self, propagator=None): 519 if not hasattr(self, 'glyphs'): 520 self.glyphs = [] 521 522 def postRead(self, rawTable, font): 523 if self.Format == 1: 524 # TODO only allow glyphs that are valid? 525 self.glyphs = rawTable["GlyphArray"] 526 elif self.Format == 2: 527 glyphs = self.glyphs = [] 528 ranges = rawTable["RangeRecord"] 529 glyphOrder = font.getGlyphOrder() 530 # Some SIL fonts have coverage entries that don't have sorted 531 # StartCoverageIndex. If it is so, fixup and warn. We undo 532 # this when writing font out. 533 sorted_ranges = sorted(ranges, key=lambda a: a.StartCoverageIndex) 534 if ranges != sorted_ranges: 535 log.warning("GSUB/GPOS Coverage is not sorted by glyph ids.") 536 ranges = sorted_ranges 537 del sorted_ranges 538 for r in ranges: 539 assert r.StartCoverageIndex == len(glyphs), \ 540 (r.StartCoverageIndex, len(glyphs)) 541 start = r.Start 542 end = r.End 543 try: 544 startID = font.getGlyphID(start, requireReal=True) 545 except KeyError: 546 log.warning("Coverage table has start glyph ID out of range: %s.", start) 547 continue 548 try: 549 endID = font.getGlyphID(end, requireReal=True) + 1 550 except KeyError: 551 # Apparently some tools use 65535 to "match all" the range 552 if end != 'glyph65535': 553 log.warning("Coverage table has end glyph ID out of range: %s.", end) 554 # NOTE: We clobber out-of-range things here. There are legit uses for those, 555 # but none that we have seen in the wild. 556 endID = len(glyphOrder) 557 glyphs.extend(glyphOrder[glyphID] for glyphID in range(startID, endID)) 558 else: 559 self.glyphs = [] 560 log.warning("Unknown Coverage format: %s", self.Format) 561 del self.Format # Don't need this anymore 562 563 def preWrite(self, font): 564 glyphs = getattr(self, "glyphs", None) 565 if glyphs is None: 566 glyphs = self.glyphs = [] 567 format = 1 568 rawTable = {"GlyphArray": glyphs} 569 getGlyphID = font.getGlyphID 570 if glyphs: 571 # find out whether Format 2 is more compact or not 572 glyphIDs = [getGlyphID(glyphName) for glyphName in glyphs ] 573 brokenOrder = sorted(glyphIDs) != glyphIDs 574 575 last = glyphIDs[0] 576 ranges = [[last]] 577 for glyphID in glyphIDs[1:]: 578 if glyphID != last + 1: 579 ranges[-1].append(last) 580 ranges.append([glyphID]) 581 last = glyphID 582 ranges[-1].append(last) 583 584 if brokenOrder or len(ranges) * 3 < len(glyphs): # 3 words vs. 1 word 585 # Format 2 is more compact 586 index = 0 587 for i in range(len(ranges)): 588 start, end = ranges[i] 589 r = RangeRecord() 590 r.StartID = start 591 r.Start = font.getGlyphName(start) 592 r.End = font.getGlyphName(end) 593 r.StartCoverageIndex = index 594 ranges[i] = r 595 index = index + end - start + 1 596 if brokenOrder: 597 log.warning("GSUB/GPOS Coverage is not sorted by glyph ids.") 598 ranges.sort(key=lambda a: a.StartID) 599 for r in ranges: 600 del r.StartID 601 format = 2 602 rawTable = {"RangeRecord": ranges} 603 #else: 604 # fallthrough; Format 1 is more compact 605 self.Format = format 606 return rawTable 607 608 def toXML2(self, xmlWriter, font): 609 for glyphName in getattr(self, "glyphs", []): 610 xmlWriter.simpletag("Glyph", value=glyphName) 611 xmlWriter.newline() 612 613 def fromXML(self, name, attrs, content, font): 614 glyphs = getattr(self, "glyphs", None) 615 if glyphs is None: 616 glyphs = [] 617 self.glyphs = glyphs 618 glyphs.append(attrs["value"]) 619 620 621class VarIdxMap(BaseTable): 622 623 def populateDefaults(self, propagator=None): 624 if not hasattr(self, 'mapping'): 625 self.mapping = {} 626 627 def postRead(self, rawTable, font): 628 assert (rawTable['EntryFormat'] & 0xFFC0) == 0 629 glyphOrder = font.getGlyphOrder() 630 mapList = rawTable['mapping'] 631 mapList.extend([mapList[-1]] * (len(glyphOrder) - len(mapList))) 632 self.mapping = dict(zip(glyphOrder, mapList)) 633 634 def preWrite(self, font): 635 mapping = getattr(self, "mapping", None) 636 if mapping is None: 637 mapping = self.mapping = {} 638 639 glyphOrder = font.getGlyphOrder() 640 mapping = [mapping[g] for g in glyphOrder] 641 while len(mapping) > 1 and mapping[-2] == mapping[-1]: 642 del mapping[-1] 643 644 rawTable = { 'mapping': mapping } 645 rawTable['MappingCount'] = len(mapping) 646 647 ored = 0 648 for idx in mapping: 649 ored |= idx 650 651 inner = ored & 0xFFFF 652 innerBits = 0 653 while inner: 654 innerBits += 1 655 inner >>= 1 656 innerBits = max(innerBits, 1) 657 assert innerBits <= 16 658 659 ored = (ored >> (16-innerBits)) | (ored & ((1<<innerBits)-1)) 660 if ored <= 0x000000FF: 661 entrySize = 1 662 elif ored <= 0x0000FFFF: 663 entrySize = 2 664 elif ored <= 0x00FFFFFF: 665 entrySize = 3 666 else: 667 entrySize = 4 668 669 entryFormat = ((entrySize - 1) << 4) | (innerBits - 1) 670 671 rawTable['EntryFormat'] = entryFormat 672 return rawTable 673 674 def toXML2(self, xmlWriter, font): 675 for glyph, value in sorted(getattr(self, "mapping", {}).items()): 676 attrs = ( 677 ('glyph', glyph), 678 ('outer', value >> 16), 679 ('inner', value & 0xFFFF), 680 ) 681 xmlWriter.simpletag("Map", attrs) 682 xmlWriter.newline() 683 684 def fromXML(self, name, attrs, content, font): 685 mapping = getattr(self, "mapping", None) 686 if mapping is None: 687 mapping = {} 688 self.mapping = mapping 689 try: 690 glyph = attrs['glyph'] 691 except: # https://github.com/fonttools/fonttools/commit/21cbab8ce9ded3356fef3745122da64dcaf314e9#commitcomment-27649836 692 glyph = font.getGlyphOrder()[attrs['index']] 693 outer = safeEval(attrs['outer']) 694 inner = safeEval(attrs['inner']) 695 assert inner <= 0xFFFF 696 mapping[glyph] = (outer << 16) | inner 697 698 699class VarRegionList(BaseTable): 700 701 def preWrite(self, font): 702 # The OT spec says VarStore.VarRegionList.RegionAxisCount should always 703 # be equal to the fvar.axisCount, and OTS < v8.0.0 enforces this rule 704 # even when the VarRegionList is empty. We can't treat RegionAxisCount 705 # like a normal propagated count (== len(Region[i].VarRegionAxis)), 706 # otherwise it would default to 0 if VarRegionList is empty. 707 # Thus, we force it to always be equal to fvar.axisCount. 708 # https://github.com/khaledhosny/ots/pull/192 709 fvarTable = font.get("fvar") 710 if fvarTable: 711 self.RegionAxisCount = len(fvarTable.axes) 712 return { 713 **self.__dict__, 714 "RegionAxisCount": CountReference(self.__dict__, "RegionAxisCount") 715 } 716 717 718class SingleSubst(FormatSwitchingBaseTable): 719 720 def populateDefaults(self, propagator=None): 721 if not hasattr(self, 'mapping'): 722 self.mapping = {} 723 724 def postRead(self, rawTable, font): 725 mapping = {} 726 input = _getGlyphsFromCoverageTable(rawTable["Coverage"]) 727 if self.Format == 1: 728 delta = rawTable["DeltaGlyphID"] 729 inputGIDS = [ font.getGlyphID(name) for name in input ] 730 outGIDS = [ (glyphID + delta) % 65536 for glyphID in inputGIDS ] 731 outNames = [ font.getGlyphName(glyphID) for glyphID in outGIDS ] 732 for inp, out in zip(input, outNames): 733 mapping[inp] = out 734 elif self.Format == 2: 735 assert len(input) == rawTable["GlyphCount"], \ 736 "invalid SingleSubstFormat2 table" 737 subst = rawTable["Substitute"] 738 for inp, sub in zip(input, subst): 739 mapping[inp] = sub 740 else: 741 assert 0, "unknown format: %s" % self.Format 742 self.mapping = mapping 743 del self.Format # Don't need this anymore 744 745 def preWrite(self, font): 746 mapping = getattr(self, "mapping", None) 747 if mapping is None: 748 mapping = self.mapping = {} 749 items = list(mapping.items()) 750 getGlyphID = font.getGlyphID 751 gidItems = [(getGlyphID(a), getGlyphID(b)) for a,b in items] 752 sortableItems = sorted(zip(gidItems, items)) 753 754 # figure out format 755 format = 2 756 delta = None 757 for inID, outID in gidItems: 758 if delta is None: 759 delta = (outID - inID) % 65536 760 761 if (inID + delta) % 65536 != outID: 762 break 763 else: 764 if delta is None: 765 # the mapping is empty, better use format 2 766 format = 2 767 else: 768 format = 1 769 770 rawTable = {} 771 self.Format = format 772 cov = Coverage() 773 input = [ item [1][0] for item in sortableItems] 774 subst = [ item [1][1] for item in sortableItems] 775 cov.glyphs = input 776 rawTable["Coverage"] = cov 777 if format == 1: 778 assert delta is not None 779 rawTable["DeltaGlyphID"] = delta 780 else: 781 rawTable["Substitute"] = subst 782 return rawTable 783 784 def toXML2(self, xmlWriter, font): 785 items = sorted(self.mapping.items()) 786 for inGlyph, outGlyph in items: 787 xmlWriter.simpletag("Substitution", 788 [("in", inGlyph), ("out", outGlyph)]) 789 xmlWriter.newline() 790 791 def fromXML(self, name, attrs, content, font): 792 mapping = getattr(self, "mapping", None) 793 if mapping is None: 794 mapping = {} 795 self.mapping = mapping 796 mapping[attrs["in"]] = attrs["out"] 797 798 799class MultipleSubst(FormatSwitchingBaseTable): 800 801 def populateDefaults(self, propagator=None): 802 if not hasattr(self, 'mapping'): 803 self.mapping = {} 804 805 def postRead(self, rawTable, font): 806 mapping = {} 807 if self.Format == 1: 808 glyphs = _getGlyphsFromCoverageTable(rawTable["Coverage"]) 809 subst = [s.Substitute for s in rawTable["Sequence"]] 810 mapping = dict(zip(glyphs, subst)) 811 else: 812 assert 0, "unknown format: %s" % self.Format 813 self.mapping = mapping 814 del self.Format # Don't need this anymore 815 816 def preWrite(self, font): 817 mapping = getattr(self, "mapping", None) 818 if mapping is None: 819 mapping = self.mapping = {} 820 cov = Coverage() 821 cov.glyphs = sorted(list(mapping.keys()), key=font.getGlyphID) 822 self.Format = 1 823 rawTable = { 824 "Coverage": cov, 825 "Sequence": [self.makeSequence_(mapping[glyph]) 826 for glyph in cov.glyphs], 827 } 828 return rawTable 829 830 def toXML2(self, xmlWriter, font): 831 items = sorted(self.mapping.items()) 832 for inGlyph, outGlyphs in items: 833 out = ",".join(outGlyphs) 834 xmlWriter.simpletag("Substitution", 835 [("in", inGlyph), ("out", out)]) 836 xmlWriter.newline() 837 838 def fromXML(self, name, attrs, content, font): 839 mapping = getattr(self, "mapping", None) 840 if mapping is None: 841 mapping = {} 842 self.mapping = mapping 843 844 # TTX v3.0 and earlier. 845 if name == "Coverage": 846 self.old_coverage_ = [] 847 for element in content: 848 if not isinstance(element, tuple): 849 continue 850 element_name, element_attrs, _ = element 851 if element_name == "Glyph": 852 self.old_coverage_.append(element_attrs["value"]) 853 return 854 if name == "Sequence": 855 index = int(attrs.get("index", len(mapping))) 856 glyph = self.old_coverage_[index] 857 glyph_mapping = mapping[glyph] = [] 858 for element in content: 859 if not isinstance(element, tuple): 860 continue 861 element_name, element_attrs, _ = element 862 if element_name == "Substitute": 863 glyph_mapping.append(element_attrs["value"]) 864 return 865 866 # TTX v3.1 and later. 867 outGlyphs = attrs["out"].split(",") if attrs["out"] else [] 868 mapping[attrs["in"]] = [g.strip() for g in outGlyphs] 869 870 @staticmethod 871 def makeSequence_(g): 872 seq = Sequence() 873 seq.Substitute = g 874 return seq 875 876 877class ClassDef(FormatSwitchingBaseTable): 878 879 def populateDefaults(self, propagator=None): 880 if not hasattr(self, 'classDefs'): 881 self.classDefs = {} 882 883 def postRead(self, rawTable, font): 884 classDefs = {} 885 glyphOrder = font.getGlyphOrder() 886 887 if self.Format == 1: 888 start = rawTable["StartGlyph"] 889 classList = rawTable["ClassValueArray"] 890 try: 891 startID = font.getGlyphID(start, requireReal=True) 892 except KeyError: 893 log.warning("ClassDef table has start glyph ID out of range: %s.", start) 894 startID = len(glyphOrder) 895 endID = startID + len(classList) 896 if endID > len(glyphOrder): 897 log.warning("ClassDef table has entries for out of range glyph IDs: %s,%s.", 898 start, len(classList)) 899 # NOTE: We clobber out-of-range things here. There are legit uses for those, 900 # but none that we have seen in the wild. 901 endID = len(glyphOrder) 902 903 for glyphID, cls in zip(range(startID, endID), classList): 904 if cls: 905 classDefs[glyphOrder[glyphID]] = cls 906 907 elif self.Format == 2: 908 records = rawTable["ClassRangeRecord"] 909 for rec in records: 910 start = rec.Start 911 end = rec.End 912 cls = rec.Class 913 try: 914 startID = font.getGlyphID(start, requireReal=True) 915 except KeyError: 916 log.warning("ClassDef table has start glyph ID out of range: %s.", start) 917 continue 918 try: 919 endID = font.getGlyphID(end, requireReal=True) + 1 920 except KeyError: 921 # Apparently some tools use 65535 to "match all" the range 922 if end != 'glyph65535': 923 log.warning("ClassDef table has end glyph ID out of range: %s.", end) 924 # NOTE: We clobber out-of-range things here. There are legit uses for those, 925 # but none that we have seen in the wild. 926 endID = len(glyphOrder) 927 for glyphID in range(startID, endID): 928 if cls: 929 classDefs[glyphOrder[glyphID]] = cls 930 else: 931 log.warning("Unknown ClassDef format: %s", self.Format) 932 self.classDefs = classDefs 933 del self.Format # Don't need this anymore 934 935 def _getClassRanges(self, font): 936 classDefs = getattr(self, "classDefs", None) 937 if classDefs is None: 938 self.classDefs = {} 939 return 940 getGlyphID = font.getGlyphID 941 items = [] 942 for glyphName, cls in classDefs.items(): 943 if not cls: 944 continue 945 items.append((getGlyphID(glyphName), glyphName, cls)) 946 if items: 947 items.sort() 948 last, lastName, lastCls = items[0] 949 ranges = [[lastCls, last, lastName]] 950 for glyphID, glyphName, cls in items[1:]: 951 if glyphID != last + 1 or cls != lastCls: 952 ranges[-1].extend([last, lastName]) 953 ranges.append([cls, glyphID, glyphName]) 954 last = glyphID 955 lastName = glyphName 956 lastCls = cls 957 ranges[-1].extend([last, lastName]) 958 return ranges 959 960 def preWrite(self, font): 961 format = 2 962 rawTable = {"ClassRangeRecord": []} 963 ranges = self._getClassRanges(font) 964 if ranges: 965 startGlyph = ranges[0][1] 966 endGlyph = ranges[-1][3] 967 glyphCount = endGlyph - startGlyph + 1 968 if len(ranges) * 3 < glyphCount + 1: 969 # Format 2 is more compact 970 for i in range(len(ranges)): 971 cls, start, startName, end, endName = ranges[i] 972 rec = ClassRangeRecord() 973 rec.Start = startName 974 rec.End = endName 975 rec.Class = cls 976 ranges[i] = rec 977 format = 2 978 rawTable = {"ClassRangeRecord": ranges} 979 else: 980 # Format 1 is more compact 981 startGlyphName = ranges[0][2] 982 classes = [0] * glyphCount 983 for cls, start, startName, end, endName in ranges: 984 for g in range(start - startGlyph, end - startGlyph + 1): 985 classes[g] = cls 986 format = 1 987 rawTable = {"StartGlyph": startGlyphName, "ClassValueArray": classes} 988 self.Format = format 989 return rawTable 990 991 def toXML2(self, xmlWriter, font): 992 items = sorted(self.classDefs.items()) 993 for glyphName, cls in items: 994 xmlWriter.simpletag("ClassDef", [("glyph", glyphName), ("class", cls)]) 995 xmlWriter.newline() 996 997 def fromXML(self, name, attrs, content, font): 998 classDefs = getattr(self, "classDefs", None) 999 if classDefs is None: 1000 classDefs = {} 1001 self.classDefs = classDefs 1002 classDefs[attrs["glyph"]] = int(attrs["class"]) 1003 1004 1005class AlternateSubst(FormatSwitchingBaseTable): 1006 1007 def populateDefaults(self, propagator=None): 1008 if not hasattr(self, 'alternates'): 1009 self.alternates = {} 1010 1011 def postRead(self, rawTable, font): 1012 alternates = {} 1013 if self.Format == 1: 1014 input = _getGlyphsFromCoverageTable(rawTable["Coverage"]) 1015 alts = rawTable["AlternateSet"] 1016 assert len(input) == len(alts) 1017 for inp,alt in zip(input,alts): 1018 alternates[inp] = alt.Alternate 1019 else: 1020 assert 0, "unknown format: %s" % self.Format 1021 self.alternates = alternates 1022 del self.Format # Don't need this anymore 1023 1024 def preWrite(self, font): 1025 self.Format = 1 1026 alternates = getattr(self, "alternates", None) 1027 if alternates is None: 1028 alternates = self.alternates = {} 1029 items = list(alternates.items()) 1030 for i in range(len(items)): 1031 glyphName, set = items[i] 1032 items[i] = font.getGlyphID(glyphName), glyphName, set 1033 items.sort() 1034 cov = Coverage() 1035 cov.glyphs = [ item[1] for item in items] 1036 alternates = [] 1037 setList = [ item[-1] for item in items] 1038 for set in setList: 1039 alts = AlternateSet() 1040 alts.Alternate = set 1041 alternates.append(alts) 1042 # a special case to deal with the fact that several hundred Adobe Japan1-5 1043 # CJK fonts will overflow an offset if the coverage table isn't pushed to the end. 1044 # Also useful in that when splitting a sub-table because of an offset overflow 1045 # I don't need to calculate the change in the subtable offset due to the change in the coverage table size. 1046 # Allows packing more rules in subtable. 1047 self.sortCoverageLast = 1 1048 return {"Coverage": cov, "AlternateSet": alternates} 1049 1050 def toXML2(self, xmlWriter, font): 1051 items = sorted(self.alternates.items()) 1052 for glyphName, alternates in items: 1053 xmlWriter.begintag("AlternateSet", glyph=glyphName) 1054 xmlWriter.newline() 1055 for alt in alternates: 1056 xmlWriter.simpletag("Alternate", glyph=alt) 1057 xmlWriter.newline() 1058 xmlWriter.endtag("AlternateSet") 1059 xmlWriter.newline() 1060 1061 def fromXML(self, name, attrs, content, font): 1062 alternates = getattr(self, "alternates", None) 1063 if alternates is None: 1064 alternates = {} 1065 self.alternates = alternates 1066 glyphName = attrs["glyph"] 1067 set = [] 1068 alternates[glyphName] = set 1069 for element in content: 1070 if not isinstance(element, tuple): 1071 continue 1072 name, attrs, content = element 1073 set.append(attrs["glyph"]) 1074 1075 1076class LigatureSubst(FormatSwitchingBaseTable): 1077 1078 def populateDefaults(self, propagator=None): 1079 if not hasattr(self, 'ligatures'): 1080 self.ligatures = {} 1081 1082 def postRead(self, rawTable, font): 1083 ligatures = {} 1084 if self.Format == 1: 1085 input = _getGlyphsFromCoverageTable(rawTable["Coverage"]) 1086 ligSets = rawTable["LigatureSet"] 1087 assert len(input) == len(ligSets) 1088 for i in range(len(input)): 1089 ligatures[input[i]] = ligSets[i].Ligature 1090 else: 1091 assert 0, "unknown format: %s" % self.Format 1092 self.ligatures = ligatures 1093 del self.Format # Don't need this anymore 1094 1095 def preWrite(self, font): 1096 self.Format = 1 1097 ligatures = getattr(self, "ligatures", None) 1098 if ligatures is None: 1099 ligatures = self.ligatures = {} 1100 1101 if ligatures and isinstance(next(iter(ligatures)), tuple): 1102 # New high-level API in v3.1 and later. Note that we just support compiling this 1103 # for now. We don't load to this API, and don't do XML with it. 1104 1105 # ligatures is map from components-sequence to lig-glyph 1106 newLigatures = dict() 1107 for comps,lig in sorted(ligatures.items(), key=lambda item: (-len(item[0]), item[0])): 1108 ligature = Ligature() 1109 ligature.Component = comps[1:] 1110 ligature.CompCount = len(comps) 1111 ligature.LigGlyph = lig 1112 newLigatures.setdefault(comps[0], []).append(ligature) 1113 ligatures = newLigatures 1114 1115 items = list(ligatures.items()) 1116 for i in range(len(items)): 1117 glyphName, set = items[i] 1118 items[i] = font.getGlyphID(glyphName), glyphName, set 1119 items.sort() 1120 cov = Coverage() 1121 cov.glyphs = [ item[1] for item in items] 1122 1123 ligSets = [] 1124 setList = [ item[-1] for item in items ] 1125 for set in setList: 1126 ligSet = LigatureSet() 1127 ligs = ligSet.Ligature = [] 1128 for lig in set: 1129 ligs.append(lig) 1130 ligSets.append(ligSet) 1131 # Useful in that when splitting a sub-table because of an offset overflow 1132 # I don't need to calculate the change in subtabl offset due to the coverage table size. 1133 # Allows packing more rules in subtable. 1134 self.sortCoverageLast = 1 1135 return {"Coverage": cov, "LigatureSet": ligSets} 1136 1137 def toXML2(self, xmlWriter, font): 1138 items = sorted(self.ligatures.items()) 1139 for glyphName, ligSets in items: 1140 xmlWriter.begintag("LigatureSet", glyph=glyphName) 1141 xmlWriter.newline() 1142 for lig in ligSets: 1143 xmlWriter.simpletag("Ligature", glyph=lig.LigGlyph, 1144 components=",".join(lig.Component)) 1145 xmlWriter.newline() 1146 xmlWriter.endtag("LigatureSet") 1147 xmlWriter.newline() 1148 1149 def fromXML(self, name, attrs, content, font): 1150 ligatures = getattr(self, "ligatures", None) 1151 if ligatures is None: 1152 ligatures = {} 1153 self.ligatures = ligatures 1154 glyphName = attrs["glyph"] 1155 ligs = [] 1156 ligatures[glyphName] = ligs 1157 for element in content: 1158 if not isinstance(element, tuple): 1159 continue 1160 name, attrs, content = element 1161 lig = Ligature() 1162 lig.LigGlyph = attrs["glyph"] 1163 components = attrs["components"] 1164 lig.Component = components.split(",") if components else [] 1165 lig.CompCount = len(lig.Component) 1166 ligs.append(lig) 1167 1168 1169class COLR(BaseTable): 1170 1171 def decompile(self, reader, font): 1172 # COLRv0 is exceptional in that LayerRecordCount appears *after* the 1173 # LayerRecordArray it counts, but the parser logic expects Count fields 1174 # to always precede the arrays. Here we work around this by parsing the 1175 # LayerRecordCount before the rest of the table, and storing it in 1176 # the reader's local state. 1177 subReader = reader.getSubReader(offset=0) 1178 for conv in self.getConverters(): 1179 if conv.name != "LayerRecordCount": 1180 subReader.advance(conv.staticSize) 1181 continue 1182 conv = self.getConverterByName("LayerRecordCount") 1183 reader[conv.name] = conv.read(subReader, font, tableDict={}) 1184 break 1185 else: 1186 raise AssertionError("LayerRecordCount converter not found") 1187 return BaseTable.decompile(self, reader, font) 1188 1189 def preWrite(self, font): 1190 # The writer similarly assumes Count values precede the things counted, 1191 # thus here we pre-initialize a CountReference; the actual count value 1192 # will be set to the lenght of the array by the time this is assembled. 1193 self.LayerRecordCount = None 1194 return { 1195 **self.__dict__, 1196 "LayerRecordCount": CountReference(self.__dict__, "LayerRecordCount") 1197 } 1198 1199 1200class LookupList(BaseTable): 1201 @property 1202 def table(self): 1203 for l in self.Lookup: 1204 for st in l.SubTable: 1205 if type(st).__name__.endswith("Subst"): 1206 return "GSUB" 1207 if type(st).__name__.endswith("Pos"): 1208 return "GPOS" 1209 raise ValueError 1210 1211 def toXML2(self, xmlWriter, font): 1212 if not font or "Debg" not in font or LOOKUP_DEBUG_INFO_KEY not in font["Debg"].data: 1213 return super().toXML2(xmlWriter, font) 1214 debugData = font["Debg"].data[LOOKUP_DEBUG_INFO_KEY][self.table] 1215 for conv in self.getConverters(): 1216 if conv.repeat: 1217 value = getattr(self, conv.name, []) 1218 for lookupIndex, item in enumerate(value): 1219 if str(lookupIndex) in debugData: 1220 info = LookupDebugInfo(*debugData[str(lookupIndex)]) 1221 tag = info.location 1222 if info.name: 1223 tag = f'{info.name}: {tag}' 1224 if info.feature: 1225 script,language,feature = info.feature 1226 tag = f'{tag} in {feature} ({script}/{language})' 1227 xmlWriter.comment(tag) 1228 xmlWriter.newline() 1229 1230 conv.xmlWrite(xmlWriter, font, item, conv.name, 1231 [("index", lookupIndex)]) 1232 else: 1233 if conv.aux and not eval(conv.aux, None, vars(self)): 1234 continue 1235 value = getattr(self, conv.name, None) # TODO Handle defaults instead of defaulting to None! 1236 conv.xmlWrite(xmlWriter, font, value, conv.name, []) 1237 1238class BaseGlyphRecordArray(BaseTable): 1239 1240 def preWrite(self, font): 1241 self.BaseGlyphRecord = sorted( 1242 self.BaseGlyphRecord, 1243 key=lambda rec: font.getGlyphID(rec.BaseGlyph) 1244 ) 1245 return self.__dict__.copy() 1246 1247 1248class BaseGlyphV1List(BaseTable): 1249 1250 def preWrite(self, font): 1251 self.BaseGlyphV1Record = sorted( 1252 self.BaseGlyphV1Record, 1253 key=lambda rec: font.getGlyphID(rec.BaseGlyph) 1254 ) 1255 return self.__dict__.copy() 1256 1257 1258 1259class VariableValue(namedtuple("VariableValue", ["value", "varIdx"])): 1260 __slots__ = () 1261 1262 _value_mapper = None 1263 1264 def __new__(cls, value, varIdx=0): 1265 return super().__new__( 1266 cls, 1267 cls._value_mapper(value) if cls._value_mapper else value, 1268 varIdx 1269 ) 1270 1271 @classmethod 1272 def _make(cls, iterable): 1273 if cls._value_mapper: 1274 it = iter(iterable) 1275 try: 1276 value = next(it) 1277 except StopIteration: 1278 pass 1279 else: 1280 value = cls._value_mapper(value) 1281 iterable = itertools.chain((value,), it) 1282 return super()._make(iterable) 1283 1284 1285class VariableFloat(VariableValue): 1286 __slots__ = () 1287 _value_mapper = float 1288 1289 1290class VariableInt(VariableValue): 1291 __slots__ = () 1292 _value_mapper = otRound 1293 1294 1295class ExtendMode(IntEnum): 1296 PAD = 0 1297 REPEAT = 1 1298 REFLECT = 2 1299 1300 1301# Porter-Duff modes for COLRv1 PaintComposite: 1302# https://github.com/googlefonts/colr-gradients-spec/tree/off_sub_1#compositemode-enumeration 1303class CompositeMode(IntEnum): 1304 CLEAR = 0 1305 SRC = 1 1306 DEST = 2 1307 SRC_OVER = 3 1308 DEST_OVER = 4 1309 SRC_IN = 5 1310 DEST_IN = 6 1311 SRC_OUT = 7 1312 DEST_OUT = 8 1313 SRC_ATOP = 9 1314 DEST_ATOP = 10 1315 XOR = 11 1316 SCREEN = 12 1317 OVERLAY = 13 1318 DARKEN = 14 1319 LIGHTEN = 15 1320 COLOR_DODGE = 16 1321 COLOR_BURN = 17 1322 HARD_LIGHT = 18 1323 SOFT_LIGHT = 19 1324 DIFFERENCE = 20 1325 EXCLUSION = 21 1326 MULTIPLY = 22 1327 HSL_HUE = 23 1328 HSL_SATURATION = 24 1329 HSL_COLOR = 25 1330 HSL_LUMINOSITY = 26 1331 1332 1333class PaintFormat(IntEnum): 1334 PaintColrLayers = 1 1335 PaintSolid = 2 1336 PaintVarSolid = 3, 1337 PaintLinearGradient = 4 1338 PaintVarLinearGradient = 5 1339 PaintRadialGradient = 6 1340 PaintVarRadialGradient = 7 1341 PaintSweepGradient = 8 1342 PaintVarSweepGradient = 9 1343 PaintGlyph = 10 1344 PaintColrGlyph = 11 1345 PaintTransform = 12 1346 PaintVarTransform = 13 1347 PaintTranslate = 14 1348 PaintVarTranslate = 15 1349 PaintRotate = 16 1350 PaintVarRotate = 17 1351 PaintSkew = 18 1352 PaintVarSkew = 19 1353 PaintComposite = 20 1354 1355 1356class Paint(getFormatSwitchingBaseTableClass("uint8")): 1357 1358 def getFormatName(self): 1359 try: 1360 return PaintFormat(self.Format).name 1361 except ValueError: 1362 raise NotImplementedError(f"Unknown Paint format: {self.Format}") 1363 1364 def toXML(self, xmlWriter, font, attrs=None, name=None): 1365 tableName = name if name else self.__class__.__name__ 1366 if attrs is None: 1367 attrs = [] 1368 attrs.append(("Format", self.Format)) 1369 xmlWriter.begintag(tableName, attrs) 1370 xmlWriter.comment(self.getFormatName()) 1371 xmlWriter.newline() 1372 self.toXML2(xmlWriter, font) 1373 xmlWriter.endtag(tableName) 1374 xmlWriter.newline() 1375 1376 def getChildren(self, colr): 1377 if self.Format == PaintFormat.PaintColrLayers: 1378 return colr.LayerV1List.Paint[ 1379 self.FirstLayerIndex : self.FirstLayerIndex + self.NumLayers 1380 ] 1381 1382 if self.Format == PaintFormat.PaintColrGlyph: 1383 for record in colr.BaseGlyphV1List.BaseGlyphV1Record: 1384 if record.BaseGlyph == self.Glyph: 1385 return [record.Paint] 1386 else: 1387 raise KeyError(f"{self.Glyph!r} not in colr.BaseGlyphV1List") 1388 1389 children = [] 1390 for conv in self.getConverters(): 1391 if conv.tableClass is not None and issubclass(conv.tableClass, type(self)): 1392 children.append(getattr(self, conv.name)) 1393 1394 return children 1395 1396 def traverse(self, colr: COLR, callback): 1397 """Depth-first traversal of graph rooted at self, callback on each node.""" 1398 if not callable(callback): 1399 raise TypeError("callback must be callable") 1400 stack = [self] 1401 visited = set() 1402 while stack: 1403 current = stack.pop() 1404 if id(current) in visited: 1405 continue 1406 callback(current) 1407 visited.add(id(current)) 1408 stack.extend(reversed(current.getChildren(colr))) 1409 1410 1411# For each subtable format there is a class. However, we don't really distinguish 1412# between "field name" and "format name": often these are the same. Yet there's 1413# a whole bunch of fields with different names. The following dict is a mapping 1414# from "format name" to "field name". _buildClasses() uses this to create a 1415# subclass for each alternate field name. 1416# 1417_equivalents = { 1418 'MarkArray': ("Mark1Array",), 1419 'LangSys': ('DefaultLangSys',), 1420 'Coverage': ('MarkCoverage', 'BaseCoverage', 'LigatureCoverage', 'Mark1Coverage', 1421 'Mark2Coverage', 'BacktrackCoverage', 'InputCoverage', 1422 'LookAheadCoverage', 'VertGlyphCoverage', 'HorizGlyphCoverage', 1423 'TopAccentCoverage', 'ExtendedShapeCoverage', 'MathKernCoverage'), 1424 'ClassDef': ('ClassDef1', 'ClassDef2', 'BacktrackClassDef', 'InputClassDef', 1425 'LookAheadClassDef', 'GlyphClassDef', 'MarkAttachClassDef'), 1426 'Anchor': ('EntryAnchor', 'ExitAnchor', 'BaseAnchor', 'LigatureAnchor', 1427 'Mark2Anchor', 'MarkAnchor'), 1428 'Device': ('XPlaDevice', 'YPlaDevice', 'XAdvDevice', 'YAdvDevice', 1429 'XDeviceTable', 'YDeviceTable', 'DeviceTable'), 1430 'Axis': ('HorizAxis', 'VertAxis',), 1431 'MinMax': ('DefaultMinMax',), 1432 'BaseCoord': ('MinCoord', 'MaxCoord',), 1433 'JstfLangSys': ('DefJstfLangSys',), 1434 'JstfGSUBModList': ('ShrinkageEnableGSUB', 'ShrinkageDisableGSUB', 'ExtensionEnableGSUB', 1435 'ExtensionDisableGSUB',), 1436 'JstfGPOSModList': ('ShrinkageEnableGPOS', 'ShrinkageDisableGPOS', 'ExtensionEnableGPOS', 1437 'ExtensionDisableGPOS',), 1438 'JstfMax': ('ShrinkageJstfMax', 'ExtensionJstfMax',), 1439 'MathKern': ('TopRightMathKern', 'TopLeftMathKern', 'BottomRightMathKern', 1440 'BottomLeftMathKern'), 1441 'MathGlyphConstruction': ('VertGlyphConstruction', 'HorizGlyphConstruction'), 1442} 1443 1444# 1445# OverFlow logic, to automatically create ExtensionLookups 1446# XXX This should probably move to otBase.py 1447# 1448 1449def fixLookupOverFlows(ttf, overflowRecord): 1450 """ Either the offset from the LookupList to a lookup overflowed, or 1451 an offset from a lookup to a subtable overflowed. 1452 The table layout is: 1453 GPSO/GUSB 1454 Script List 1455 Feature List 1456 LookUpList 1457 Lookup[0] and contents 1458 SubTable offset list 1459 SubTable[0] and contents 1460 ... 1461 SubTable[n] and contents 1462 ... 1463 Lookup[n] and contents 1464 SubTable offset list 1465 SubTable[0] and contents 1466 ... 1467 SubTable[n] and contents 1468 If the offset to a lookup overflowed (SubTableIndex is None) 1469 we must promote the *previous* lookup to an Extension type. 1470 If the offset from a lookup to subtable overflowed, then we must promote it 1471 to an Extension Lookup type. 1472 """ 1473 ok = 0 1474 lookupIndex = overflowRecord.LookupListIndex 1475 if (overflowRecord.SubTableIndex is None): 1476 lookupIndex = lookupIndex - 1 1477 if lookupIndex < 0: 1478 return ok 1479 if overflowRecord.tableType == 'GSUB': 1480 extType = 7 1481 elif overflowRecord.tableType == 'GPOS': 1482 extType = 9 1483 1484 lookups = ttf[overflowRecord.tableType].table.LookupList.Lookup 1485 lookup = lookups[lookupIndex] 1486 # If the previous lookup is an extType, look further back. Very unlikely, but possible. 1487 while lookup.SubTable[0].__class__.LookupType == extType: 1488 lookupIndex = lookupIndex -1 1489 if lookupIndex < 0: 1490 return ok 1491 lookup = lookups[lookupIndex] 1492 1493 lookup.LookupType = extType 1494 for si in range(len(lookup.SubTable)): 1495 subTable = lookup.SubTable[si] 1496 extSubTableClass = lookupTypes[overflowRecord.tableType][extType] 1497 extSubTable = extSubTableClass() 1498 extSubTable.Format = 1 1499 extSubTable.ExtSubTable = subTable 1500 lookup.SubTable[si] = extSubTable 1501 ok = 1 1502 return ok 1503 1504def splitMultipleSubst(oldSubTable, newSubTable, overflowRecord): 1505 ok = 1 1506 newSubTable.Format = oldSubTable.Format 1507 oldMapping = sorted(oldSubTable.mapping.items()) 1508 oldLen = len(oldMapping) 1509 1510 if overflowRecord.itemName in ['Coverage', 'RangeRecord']: 1511 # Coverage table is written last. Overflow is to or within the 1512 # the coverage table. We will just cut the subtable in half. 1513 newLen = oldLen // 2 1514 1515 elif overflowRecord.itemName == 'Sequence': 1516 # We just need to back up by two items from the overflowed 1517 # Sequence index to make sure the offset to the Coverage table 1518 # doesn't overflow. 1519 newLen = overflowRecord.itemIndex - 1 1520 1521 newSubTable.mapping = {} 1522 for i in range(newLen, oldLen): 1523 item = oldMapping[i] 1524 key = item[0] 1525 newSubTable.mapping[key] = item[1] 1526 del oldSubTable.mapping[key] 1527 1528 return ok 1529 1530def splitAlternateSubst(oldSubTable, newSubTable, overflowRecord): 1531 ok = 1 1532 newSubTable.Format = oldSubTable.Format 1533 if hasattr(oldSubTable, 'sortCoverageLast'): 1534 newSubTable.sortCoverageLast = oldSubTable.sortCoverageLast 1535 1536 oldAlts = sorted(oldSubTable.alternates.items()) 1537 oldLen = len(oldAlts) 1538 1539 if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']: 1540 # Coverage table is written last. overflow is to or within the 1541 # the coverage table. We will just cut the subtable in half. 1542 newLen = oldLen//2 1543 1544 elif overflowRecord.itemName == 'AlternateSet': 1545 # We just need to back up by two items 1546 # from the overflowed AlternateSet index to make sure the offset 1547 # to the Coverage table doesn't overflow. 1548 newLen = overflowRecord.itemIndex - 1 1549 1550 newSubTable.alternates = {} 1551 for i in range(newLen, oldLen): 1552 item = oldAlts[i] 1553 key = item[0] 1554 newSubTable.alternates[key] = item[1] 1555 del oldSubTable.alternates[key] 1556 1557 return ok 1558 1559 1560def splitLigatureSubst(oldSubTable, newSubTable, overflowRecord): 1561 ok = 1 1562 newSubTable.Format = oldSubTable.Format 1563 oldLigs = sorted(oldSubTable.ligatures.items()) 1564 oldLen = len(oldLigs) 1565 1566 if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']: 1567 # Coverage table is written last. overflow is to or within the 1568 # the coverage table. We will just cut the subtable in half. 1569 newLen = oldLen//2 1570 1571 elif overflowRecord.itemName == 'LigatureSet': 1572 # We just need to back up by two items 1573 # from the overflowed AlternateSet index to make sure the offset 1574 # to the Coverage table doesn't overflow. 1575 newLen = overflowRecord.itemIndex - 1 1576 1577 newSubTable.ligatures = {} 1578 for i in range(newLen, oldLen): 1579 item = oldLigs[i] 1580 key = item[0] 1581 newSubTable.ligatures[key] = item[1] 1582 del oldSubTable.ligatures[key] 1583 1584 return ok 1585 1586 1587def splitPairPos(oldSubTable, newSubTable, overflowRecord): 1588 st = oldSubTable 1589 ok = False 1590 newSubTable.Format = oldSubTable.Format 1591 if oldSubTable.Format == 1 and len(oldSubTable.PairSet) > 1: 1592 for name in 'ValueFormat1', 'ValueFormat2': 1593 setattr(newSubTable, name, getattr(oldSubTable, name)) 1594 1595 # Move top half of coverage to new subtable 1596 1597 newSubTable.Coverage = oldSubTable.Coverage.__class__() 1598 1599 coverage = oldSubTable.Coverage.glyphs 1600 records = oldSubTable.PairSet 1601 1602 oldCount = len(oldSubTable.PairSet) // 2 1603 1604 oldSubTable.Coverage.glyphs = coverage[:oldCount] 1605 oldSubTable.PairSet = records[:oldCount] 1606 1607 newSubTable.Coverage.glyphs = coverage[oldCount:] 1608 newSubTable.PairSet = records[oldCount:] 1609 1610 oldSubTable.PairSetCount = len(oldSubTable.PairSet) 1611 newSubTable.PairSetCount = len(newSubTable.PairSet) 1612 1613 ok = True 1614 1615 elif oldSubTable.Format == 2 and len(oldSubTable.Class1Record) > 1: 1616 if not hasattr(oldSubTable, 'Class2Count'): 1617 oldSubTable.Class2Count = len(oldSubTable.Class1Record[0].Class2Record) 1618 for name in 'Class2Count', 'ClassDef2', 'ValueFormat1', 'ValueFormat2': 1619 setattr(newSubTable, name, getattr(oldSubTable, name)) 1620 1621 # The two subtables will still have the same ClassDef2 and the table 1622 # sharing will still cause the sharing to overflow. As such, disable 1623 # sharing on the one that is serialized second (that's oldSubTable). 1624 oldSubTable.DontShare = True 1625 1626 # Move top half of class numbers to new subtable 1627 1628 newSubTable.Coverage = oldSubTable.Coverage.__class__() 1629 newSubTable.ClassDef1 = oldSubTable.ClassDef1.__class__() 1630 1631 coverage = oldSubTable.Coverage.glyphs 1632 classDefs = oldSubTable.ClassDef1.classDefs 1633 records = oldSubTable.Class1Record 1634 1635 oldCount = len(oldSubTable.Class1Record) // 2 1636 newGlyphs = set(k for k,v in classDefs.items() if v >= oldCount) 1637 1638 oldSubTable.Coverage.glyphs = [g for g in coverage if g not in newGlyphs] 1639 oldSubTable.ClassDef1.classDefs = {k:v for k,v in classDefs.items() if v < oldCount} 1640 oldSubTable.Class1Record = records[:oldCount] 1641 1642 newSubTable.Coverage.glyphs = [g for g in coverage if g in newGlyphs] 1643 newSubTable.ClassDef1.classDefs = {k:(v-oldCount) for k,v in classDefs.items() if v > oldCount} 1644 newSubTable.Class1Record = records[oldCount:] 1645 1646 oldSubTable.Class1Count = len(oldSubTable.Class1Record) 1647 newSubTable.Class1Count = len(newSubTable.Class1Record) 1648 1649 ok = True 1650 1651 return ok 1652 1653 1654def splitMarkBasePos(oldSubTable, newSubTable, overflowRecord): 1655 # split half of the mark classes to the new subtable 1656 classCount = oldSubTable.ClassCount 1657 if classCount < 2: 1658 # oh well, not much left to split... 1659 return False 1660 1661 oldClassCount = classCount // 2 1662 newClassCount = classCount - oldClassCount 1663 1664 oldMarkCoverage, oldMarkRecords = [], [] 1665 newMarkCoverage, newMarkRecords = [], [] 1666 for glyphName, markRecord in zip( 1667 oldSubTable.MarkCoverage.glyphs, 1668 oldSubTable.MarkArray.MarkRecord 1669 ): 1670 if markRecord.Class < oldClassCount: 1671 oldMarkCoverage.append(glyphName) 1672 oldMarkRecords.append(markRecord) 1673 else: 1674 markRecord.Class -= oldClassCount 1675 newMarkCoverage.append(glyphName) 1676 newMarkRecords.append(markRecord) 1677 1678 oldBaseRecords, newBaseRecords = [], [] 1679 for rec in oldSubTable.BaseArray.BaseRecord: 1680 oldBaseRecord, newBaseRecord = rec.__class__(), rec.__class__() 1681 oldBaseRecord.BaseAnchor = rec.BaseAnchor[:oldClassCount] 1682 newBaseRecord.BaseAnchor = rec.BaseAnchor[oldClassCount:] 1683 oldBaseRecords.append(oldBaseRecord) 1684 newBaseRecords.append(newBaseRecord) 1685 1686 newSubTable.Format = oldSubTable.Format 1687 1688 oldSubTable.MarkCoverage.glyphs = oldMarkCoverage 1689 newSubTable.MarkCoverage = oldSubTable.MarkCoverage.__class__() 1690 newSubTable.MarkCoverage.glyphs = newMarkCoverage 1691 1692 # share the same BaseCoverage in both halves 1693 newSubTable.BaseCoverage = oldSubTable.BaseCoverage 1694 1695 oldSubTable.ClassCount = oldClassCount 1696 newSubTable.ClassCount = newClassCount 1697 1698 oldSubTable.MarkArray.MarkRecord = oldMarkRecords 1699 newSubTable.MarkArray = oldSubTable.MarkArray.__class__() 1700 newSubTable.MarkArray.MarkRecord = newMarkRecords 1701 1702 oldSubTable.MarkArray.MarkCount = len(oldMarkRecords) 1703 newSubTable.MarkArray.MarkCount = len(newMarkRecords) 1704 1705 oldSubTable.BaseArray.BaseRecord = oldBaseRecords 1706 newSubTable.BaseArray = oldSubTable.BaseArray.__class__() 1707 newSubTable.BaseArray.BaseRecord = newBaseRecords 1708 1709 oldSubTable.BaseArray.BaseCount = len(oldBaseRecords) 1710 newSubTable.BaseArray.BaseCount = len(newBaseRecords) 1711 1712 return True 1713 1714 1715splitTable = { 'GSUB': { 1716# 1: splitSingleSubst, 1717 2: splitMultipleSubst, 1718 3: splitAlternateSubst, 1719 4: splitLigatureSubst, 1720# 5: splitContextSubst, 1721# 6: splitChainContextSubst, 1722# 7: splitExtensionSubst, 1723# 8: splitReverseChainSingleSubst, 1724 }, 1725 'GPOS': { 1726# 1: splitSinglePos, 1727 2: splitPairPos, 1728# 3: splitCursivePos, 1729 4: splitMarkBasePos, 1730# 5: splitMarkLigPos, 1731# 6: splitMarkMarkPos, 1732# 7: splitContextPos, 1733# 8: splitChainContextPos, 1734# 9: splitExtensionPos, 1735 } 1736 1737 } 1738 1739def fixSubTableOverFlows(ttf, overflowRecord): 1740 """ 1741 An offset has overflowed within a sub-table. We need to divide this subtable into smaller parts. 1742 """ 1743 table = ttf[overflowRecord.tableType].table 1744 lookup = table.LookupList.Lookup[overflowRecord.LookupListIndex] 1745 subIndex = overflowRecord.SubTableIndex 1746 subtable = lookup.SubTable[subIndex] 1747 1748 # First, try not sharing anything for this subtable... 1749 if not hasattr(subtable, "DontShare"): 1750 subtable.DontShare = True 1751 return True 1752 1753 if hasattr(subtable, 'ExtSubTable'): 1754 # We split the subtable of the Extension table, and add a new Extension table 1755 # to contain the new subtable. 1756 1757 subTableType = subtable.ExtSubTable.__class__.LookupType 1758 extSubTable = subtable 1759 subtable = extSubTable.ExtSubTable 1760 newExtSubTableClass = lookupTypes[overflowRecord.tableType][extSubTable.__class__.LookupType] 1761 newExtSubTable = newExtSubTableClass() 1762 newExtSubTable.Format = extSubTable.Format 1763 toInsert = newExtSubTable 1764 1765 newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType] 1766 newSubTable = newSubTableClass() 1767 newExtSubTable.ExtSubTable = newSubTable 1768 else: 1769 subTableType = subtable.__class__.LookupType 1770 newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType] 1771 newSubTable = newSubTableClass() 1772 toInsert = newSubTable 1773 1774 if hasattr(lookup, 'SubTableCount'): # may not be defined yet. 1775 lookup.SubTableCount = lookup.SubTableCount + 1 1776 1777 try: 1778 splitFunc = splitTable[overflowRecord.tableType][subTableType] 1779 except KeyError: 1780 log.error( 1781 "Don't know how to split %s lookup type %s", 1782 overflowRecord.tableType, 1783 subTableType, 1784 ) 1785 return False 1786 1787 ok = splitFunc(subtable, newSubTable, overflowRecord) 1788 if ok: 1789 lookup.SubTable.insert(subIndex + 1, toInsert) 1790 return ok 1791 1792# End of OverFlow logic 1793 1794 1795def _buildClasses(): 1796 import re 1797 from .otData import otData 1798 1799 formatPat = re.compile(r"([A-Za-z0-9]+)Format(\d+)$") 1800 namespace = globals() 1801 1802 # populate module with classes 1803 for name, table in otData: 1804 baseClass = BaseTable 1805 m = formatPat.match(name) 1806 if m: 1807 # XxxFormatN subtable, we only add the "base" table 1808 name = m.group(1) 1809 # the first row of a format-switching otData table describes the Format; 1810 # the first column defines the type of the Format field. 1811 # Currently this can be either 'uint16' or 'uint8'. 1812 formatType = table[0][0] 1813 baseClass = getFormatSwitchingBaseTableClass(formatType) 1814 if name not in namespace: 1815 # the class doesn't exist yet, so the base implementation is used. 1816 cls = type(name, (baseClass,), {}) 1817 if name in ('GSUB', 'GPOS'): 1818 cls.DontShare = True 1819 namespace[name] = cls 1820 1821 for base, alts in _equivalents.items(): 1822 base = namespace[base] 1823 for alt in alts: 1824 namespace[alt] = base 1825 1826 global lookupTypes 1827 lookupTypes = { 1828 'GSUB': { 1829 1: SingleSubst, 1830 2: MultipleSubst, 1831 3: AlternateSubst, 1832 4: LigatureSubst, 1833 5: ContextSubst, 1834 6: ChainContextSubst, 1835 7: ExtensionSubst, 1836 8: ReverseChainSingleSubst, 1837 }, 1838 'GPOS': { 1839 1: SinglePos, 1840 2: PairPos, 1841 3: CursivePos, 1842 4: MarkBasePos, 1843 5: MarkLigPos, 1844 6: MarkMarkPos, 1845 7: ContextPos, 1846 8: ChainContextPos, 1847 9: ExtensionPos, 1848 }, 1849 'mort': { 1850 4: NoncontextualMorph, 1851 }, 1852 'morx': { 1853 0: RearrangementMorph, 1854 1: ContextualMorph, 1855 2: LigatureMorph, 1856 # 3: Reserved, 1857 4: NoncontextualMorph, 1858 5: InsertionMorph, 1859 }, 1860 } 1861 lookupTypes['JSTF'] = lookupTypes['GPOS'] # JSTF contains GPOS 1862 for lookupEnum in lookupTypes.values(): 1863 for enum, cls in lookupEnum.items(): 1864 cls.LookupType = enum 1865 1866 global featureParamTypes 1867 featureParamTypes = { 1868 'size': FeatureParamsSize, 1869 } 1870 for i in range(1, 20+1): 1871 featureParamTypes['ss%02d' % i] = FeatureParamsStylisticSet 1872 for i in range(1, 99+1): 1873 featureParamTypes['cv%02d' % i] = FeatureParamsCharacterVariants 1874 1875 # add converters to classes 1876 from .otConverters import buildConverters 1877 for name, table in otData: 1878 m = formatPat.match(name) 1879 if m: 1880 # XxxFormatN subtable, add converter to "base" table 1881 name, format = m.groups() 1882 format = int(format) 1883 cls = namespace[name] 1884 if not hasattr(cls, "converters"): 1885 cls.converters = {} 1886 cls.convertersByName = {} 1887 converters, convertersByName = buildConverters(table[1:], namespace) 1888 cls.converters[format] = converters 1889 cls.convertersByName[format] = convertersByName 1890 # XXX Add staticSize? 1891 else: 1892 cls = namespace[name] 1893 cls.converters, cls.convertersByName = buildConverters(table, namespace) 1894 # XXX Add staticSize? 1895 1896 1897_buildClasses() 1898 1899 1900def _getGlyphsFromCoverageTable(coverage): 1901 if coverage is None: 1902 # empty coverage table 1903 return [] 1904 else: 1905 return coverage.glyphs 1906