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