1from __future__ import print_function, division, absolute_import 2from collections import namedtuple 3from fontTools import ttLib 4from fontTools.ttLib.tables import otTables as ot 5from fontTools.ttLib.tables.otBase import ValueRecord, valueRecordFormatDict 6 7 8def buildCoverage(glyphs, glyphMap): 9 if not glyphs: 10 return None 11 self = ot.Coverage() 12 self.glyphs = sorted(glyphs, key=glyphMap.__getitem__) 13 return self 14 15 16LOOKUP_FLAG_RIGHT_TO_LEFT = 0x0001 17LOOKUP_FLAG_IGNORE_BASE_GLYPHS = 0x0002 18LOOKUP_FLAG_IGNORE_LIGATURES = 0x0004 19LOOKUP_FLAG_IGNORE_MARKS = 0x0008 20LOOKUP_FLAG_USE_MARK_FILTERING_SET = 0x0010 21 22 23def buildLookup(subtables, flags=0, markFilterSet=None): 24 if subtables is None: 25 return None 26 subtables = [st for st in subtables if st is not None] 27 if not subtables: 28 return None 29 assert all(t.LookupType == subtables[0].LookupType for t in subtables), \ 30 ("all subtables must have the same LookupType; got %s" % 31 repr([t.LookupType for t in subtables])) 32 self = ot.Lookup() 33 self.LookupType = subtables[0].LookupType 34 self.LookupFlag = flags 35 self.SubTable = subtables 36 self.SubTableCount = len(self.SubTable) 37 if markFilterSet is not None: 38 assert self.LookupFlag & LOOKUP_FLAG_USE_MARK_FILTERING_SET, \ 39 ("if markFilterSet is not None, flags must set " 40 "LOOKUP_FLAG_USE_MARK_FILTERING_SET; flags=0x%04x" % flags) 41 assert isinstance(markFilterSet, int), markFilterSet 42 self.MarkFilteringSet = markFilterSet 43 else: 44 assert (self.LookupFlag & LOOKUP_FLAG_USE_MARK_FILTERING_SET) == 0, \ 45 ("if markFilterSet is None, flags must not set " 46 "LOOKUP_FLAG_USE_MARK_FILTERING_SET; flags=0x%04x" % flags) 47 return self 48 49 50# GSUB 51 52 53def buildSingleSubstSubtable(mapping): 54 if not mapping: 55 return None 56 self = ot.SingleSubst() 57 self.mapping = dict(mapping) 58 return self 59 60 61def buildMultipleSubstSubtable(mapping): 62 if not mapping: 63 return None 64 self = ot.MultipleSubst() 65 self.mapping = dict(mapping) 66 return self 67 68 69def buildAlternateSubstSubtable(mapping): 70 if not mapping: 71 return None 72 self = ot.AlternateSubst() 73 self.alternates = dict(mapping) 74 return self 75 76 77def _getLigatureKey(components): 78 """Computes a key for ordering ligatures in a GSUB Type-4 lookup. 79 80 When building the OpenType lookup, we need to make sure that 81 the longest sequence of components is listed first, so we 82 use the negative length as the primary key for sorting. 83 To make buildLigatureSubstSubtable() deterministic, we use the 84 component sequence as the secondary key. 85 86 For example, this will sort (f,f,f) < (f,f,i) < (f,f) < (f,i) < (f,l). 87 """ 88 return (-len(components), components) 89 90 91def buildLigatureSubstSubtable(mapping): 92 if not mapping: 93 return None 94 self = ot.LigatureSubst() 95 # The following single line can replace the rest of this function 96 # with fontTools >= 3.1: 97 # self.ligatures = dict(mapping) 98 self.ligatures = {} 99 for components in sorted(mapping.keys(), key=_getLigatureKey): 100 ligature = ot.Ligature() 101 ligature.Component = components[1:] 102 ligature.CompCount = len(ligature.Component) + 1 103 ligature.LigGlyph = mapping[components] 104 firstGlyph = components[0] 105 self.ligatures.setdefault(firstGlyph, []).append(ligature) 106 return self 107 108 109# GPOS 110 111 112def buildAnchor(x, y, point=None, deviceX=None, deviceY=None): 113 self = ot.Anchor() 114 self.XCoordinate, self.YCoordinate = x, y 115 self.Format = 1 116 if point is not None: 117 self.AnchorPoint = point 118 self.Format = 2 119 if deviceX is not None or deviceY is not None: 120 assert self.Format == 1, \ 121 "Either point, or both of deviceX/deviceY, must be None." 122 self.XDeviceTable = deviceX 123 self.YDeviceTable = deviceY 124 self.Format = 3 125 return self 126 127 128def buildBaseArray(bases, numMarkClasses, glyphMap): 129 self = ot.BaseArray() 130 self.BaseRecord = [] 131 for base in sorted(bases, key=glyphMap.__getitem__): 132 b = bases[base] 133 anchors = [b.get(markClass) for markClass in range(numMarkClasses)] 134 self.BaseRecord.append(buildBaseRecord(anchors)) 135 self.BaseCount = len(self.BaseRecord) 136 return self 137 138 139def buildBaseRecord(anchors): 140 """[otTables.Anchor, otTables.Anchor, ...] --> otTables.BaseRecord""" 141 self = ot.BaseRecord() 142 self.BaseAnchor = anchors 143 return self 144 145 146def buildComponentRecord(anchors): 147 """[otTables.Anchor, otTables.Anchor, ...] --> otTables.ComponentRecord""" 148 if not anchors: 149 return None 150 self = ot.ComponentRecord() 151 self.LigatureAnchor = anchors 152 return self 153 154 155def buildCursivePosSubtable(attach, glyphMap): 156 """{"alef": (entry, exit)} --> otTables.CursivePos""" 157 if not attach: 158 return None 159 self = ot.CursivePos() 160 self.Format = 1 161 self.Coverage = buildCoverage(attach.keys(), glyphMap) 162 self.EntryExitRecord = [] 163 for glyph in self.Coverage.glyphs: 164 entryAnchor, exitAnchor = attach[glyph] 165 rec = ot.EntryExitRecord() 166 rec.EntryAnchor = entryAnchor 167 rec.ExitAnchor = exitAnchor 168 self.EntryExitRecord.append(rec) 169 self.EntryExitCount = len(self.EntryExitRecord) 170 return self 171 172 173def buildDevice(deltas): 174 """{8:+1, 10:-3, ...} --> otTables.Device""" 175 if not deltas: 176 return None 177 self = ot.Device() 178 keys = deltas.keys() 179 self.StartSize = startSize = min(keys) 180 self.EndSize = endSize = max(keys) 181 assert 0 <= startSize <= endSize 182 self.DeltaValue = deltaValues = [ 183 deltas.get(size, 0) 184 for size in range(startSize, endSize + 1)] 185 maxDelta = max(deltaValues) 186 minDelta = min(deltaValues) 187 assert minDelta > -129 and maxDelta < 128 188 if minDelta > -3 and maxDelta < 2: 189 self.DeltaFormat = 1 190 elif minDelta > -9 and maxDelta < 8: 191 self.DeltaFormat = 2 192 else: 193 self.DeltaFormat = 3 194 return self 195 196 197def buildLigatureArray(ligs, numMarkClasses, glyphMap): 198 self = ot.LigatureArray() 199 self.LigatureAttach = [] 200 for lig in sorted(ligs, key=glyphMap.__getitem__): 201 anchors = [] 202 for component in ligs[lig]: 203 anchors.append([component.get(mc) for mc in range(numMarkClasses)]) 204 self.LigatureAttach.append(buildLigatureAttach(anchors)) 205 self.LigatureCount = len(self.LigatureAttach) 206 return self 207 208 209def buildLigatureAttach(components): 210 """[[Anchor, Anchor], [Anchor, Anchor, Anchor]] --> LigatureAttach""" 211 self = ot.LigatureAttach() 212 self.ComponentRecord = [buildComponentRecord(c) for c in components] 213 self.ComponentCount = len(self.ComponentRecord) 214 return self 215 216 217def buildMarkArray(marks, glyphMap): 218 """{"acute": (markClass, otTables.Anchor)} --> otTables.MarkArray""" 219 self = ot.MarkArray() 220 self.MarkRecord = [] 221 for mark in sorted(marks.keys(), key=glyphMap.__getitem__): 222 markClass, anchor = marks[mark] 223 markrec = buildMarkRecord(markClass, anchor) 224 self.MarkRecord.append(markrec) 225 self.MarkCount = len(self.MarkRecord) 226 return self 227 228 229def buildMarkBasePos(marks, bases, glyphMap): 230 """Build a list of MarkBasePos subtables. 231 232 a1, a2, a3, a4, a5 = buildAnchor(500, 100), ... 233 marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)} 234 bases = {"a": {0: a3, 1: a5}, "b": {0: a4, 1: a5}} 235 """ 236 # TODO: Consider emitting multiple subtables to save space. 237 # Partition the marks and bases into disjoint subsets, so that 238 # MarkBasePos rules would only access glyphs from a single 239 # subset. This would likely lead to smaller mark/base 240 # matrices, so we might be able to omit many of the empty 241 # anchor tables that we currently produce. Of course, this 242 # would only work if the MarkBasePos rules of real-world fonts 243 # allow partitioning into multiple subsets. We should find out 244 # whether this is the case; if so, implement the optimization. 245 # On the other hand, a very large number of subtables could 246 # slow down layout engines; so this would need profiling. 247 return [buildMarkBasePosSubtable(marks, bases, glyphMap)] 248 249 250def buildMarkBasePosSubtable(marks, bases, glyphMap): 251 """Build a single MarkBasePos subtable. 252 253 a1, a2, a3, a4, a5 = buildAnchor(500, 100), ... 254 marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)} 255 bases = {"a": {0: a3, 1: a5}, "b": {0: a4, 1: a5}} 256 """ 257 self = ot.MarkBasePos() 258 self.Format = 1 259 self.MarkCoverage = buildCoverage(marks, glyphMap) 260 self.MarkArray = buildMarkArray(marks, glyphMap) 261 self.ClassCount = max([mc for mc, _ in marks.values()]) + 1 262 self.BaseCoverage = buildCoverage(bases, glyphMap) 263 self.BaseArray = buildBaseArray(bases, self.ClassCount, glyphMap) 264 return self 265 266 267def buildMarkLigPos(marks, ligs, glyphMap): 268 """Build a list of MarkLigPos subtables. 269 270 a1, a2, a3, a4, a5 = buildAnchor(500, 100), ... 271 marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)} 272 ligs = {"f_i": [{0: a3, 1: a5}, {0: a4, 1: a5}], "c_t": [{...}, {...}]} 273 """ 274 # TODO: Consider splitting into multiple subtables to save space, 275 # as with MarkBasePos, this would be a trade-off that would need 276 # profiling. And, depending on how typical fonts are structured, 277 # it might not be worth doing at all. 278 return [buildMarkLigPosSubtable(marks, ligs, glyphMap)] 279 280 281def buildMarkLigPosSubtable(marks, ligs, glyphMap): 282 """Build a single MarkLigPos subtable. 283 284 a1, a2, a3, a4, a5 = buildAnchor(500, 100), ... 285 marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)} 286 ligs = {"f_i": [{0: a3, 1: a5}, {0: a4, 1: a5}], "c_t": [{...}, {...}]} 287 """ 288 self = ot.MarkLigPos() 289 self.Format = 1 290 self.MarkCoverage = buildCoverage(marks, glyphMap) 291 self.MarkArray = buildMarkArray(marks, glyphMap) 292 self.ClassCount = max([mc for mc, _ in marks.values()]) + 1 293 self.LigatureCoverage = buildCoverage(ligs, glyphMap) 294 self.LigatureArray = buildLigatureArray(ligs, self.ClassCount, glyphMap) 295 return self 296 297 298def buildMarkRecord(classID, anchor): 299 assert isinstance(classID, int) 300 assert isinstance(anchor, ot.Anchor) 301 self = ot.MarkRecord() 302 self.Class = classID 303 self.MarkAnchor = anchor 304 return self 305 306 307def buildMark2Record(anchors): 308 """[otTables.Anchor, otTables.Anchor, ...] --> otTables.Mark2Record""" 309 self = ot.Mark2Record() 310 self.Mark2Anchor = anchors 311 return self 312 313 314def _getValueFormat(f, values, i): 315 """Helper for buildPairPos{Glyphs|Classes}Subtable.""" 316 if f is not None: 317 return f 318 mask = 0 319 for value in values: 320 if value is not None and value[i] is not None: 321 mask |= value[i].getFormat() 322 return mask 323 324 325def buildPairPosClassesSubtable(pairs, glyphMap, 326 valueFormat1=None, valueFormat2=None): 327 coverage = set() 328 classDef1 = ClassDefBuilder(useClass0=True) 329 classDef2 = ClassDefBuilder(useClass0=False) 330 for gc1, gc2 in sorted(pairs): 331 coverage.update(gc1) 332 classDef1.add(gc1) 333 classDef2.add(gc2) 334 self = ot.PairPos() 335 self.Format = 2 336 self.ValueFormat1 = _getValueFormat(valueFormat1, pairs.values(), 0) 337 self.ValueFormat2 = _getValueFormat(valueFormat2, pairs.values(), 1) 338 self.Coverage = buildCoverage(coverage, glyphMap) 339 self.ClassDef1 = classDef1.build() 340 self.ClassDef2 = classDef2.build() 341 classes1 = classDef1.classes() 342 classes2 = classDef2.classes() 343 self.Class1Record = [] 344 for c1 in classes1: 345 rec1 = ot.Class1Record() 346 rec1.Class2Record = [] 347 self.Class1Record.append(rec1) 348 for c2 in classes2: 349 rec2 = ot.Class2Record() 350 rec2.Value1, rec2.Value2 = pairs.get((c1, c2), (None, None)) 351 rec1.Class2Record.append(rec2) 352 self.Class1Count = len(self.Class1Record) 353 self.Class2Count = len(classes2) 354 return self 355 356 357def buildPairPosGlyphs(pairs, glyphMap): 358 p = {} # (formatA, formatB) --> {(glyphA, glyphB): (valA, valB)} 359 for (glyphA, glyphB), (valA, valB) in pairs.items(): 360 formatA = valA.getFormat() if valA is not None else 0 361 formatB = valB.getFormat() if valB is not None else 0 362 pos = p.setdefault((formatA, formatB), {}) 363 pos[(glyphA, glyphB)] = (valA, valB) 364 return [ 365 buildPairPosGlyphsSubtable(pos, glyphMap, formatA, formatB) 366 for ((formatA, formatB), pos) in sorted(p.items())] 367 368 369def buildPairPosGlyphsSubtable(pairs, glyphMap, 370 valueFormat1=None, valueFormat2=None): 371 self = ot.PairPos() 372 self.Format = 1 373 self.ValueFormat1 = _getValueFormat(valueFormat1, pairs.values(), 0) 374 self.ValueFormat2 = _getValueFormat(valueFormat2, pairs.values(), 1) 375 p = {} 376 for (glyphA, glyphB), (valA, valB) in pairs.items(): 377 p.setdefault(glyphA, []).append((glyphB, valA, valB)) 378 self.Coverage = buildCoverage({g for g, _ in pairs.keys()}, glyphMap) 379 self.PairSet = [] 380 for glyph in self.Coverage.glyphs: 381 ps = ot.PairSet() 382 ps.PairValueRecord = [] 383 self.PairSet.append(ps) 384 for glyph2, val1, val2 in \ 385 sorted(p[glyph], key=lambda x: glyphMap[x[0]]): 386 pvr = ot.PairValueRecord() 387 pvr.SecondGlyph = glyph2 388 pvr.Value1 = val1 if val1 and val1.getFormat() != 0 else None 389 pvr.Value2 = val2 if val2 and val2.getFormat() != 0 else None 390 ps.PairValueRecord.append(pvr) 391 ps.PairValueCount = len(ps.PairValueRecord) 392 self.PairSetCount = len(self.PairSet) 393 return self 394 395 396def buildSinglePos(mapping, glyphMap): 397 """{"glyph": ValueRecord} --> [otTables.SinglePos*]""" 398 result, handled = [], set() 399 # In SinglePos format 1, the covered glyphs all share the same ValueRecord. 400 # In format 2, each glyph has its own ValueRecord, but these records 401 # all have the same properties (eg., all have an X but no Y placement). 402 coverages, masks, values = {}, {}, {} 403 for glyph, value in mapping.items(): 404 key = _getSinglePosValueKey(value) 405 coverages.setdefault(key, []).append(glyph) 406 masks.setdefault(key[0], []).append(key) 407 values[key] = value 408 409 # If a ValueRecord is shared between multiple glyphs, we generate 410 # a SinglePos format 1 subtable; that is the most compact form. 411 for key, glyphs in coverages.items(): 412 # 5 ushorts is the length of introducing another sublookup 413 if len(glyphs) * _getSinglePosValueSize(key) > 5: 414 format1Mapping = {g: values[key] for g in glyphs} 415 result.append(buildSinglePosSubtable(format1Mapping, glyphMap)) 416 handled.add(key) 417 418 # In the remaining ValueRecords, look for those whose valueFormat 419 # (the set of used properties) is shared between multiple records. 420 # These will get encoded in format 2. 421 for valueFormat, keys in masks.items(): 422 f2 = [k for k in keys if k not in handled] 423 if len(f2) > 1: 424 format2Mapping = {} 425 for k in f2: 426 format2Mapping.update((g, values[k]) for g in coverages[k]) 427 result.append(buildSinglePosSubtable(format2Mapping, glyphMap)) 428 handled.update(f2) 429 430 # The remaining ValueRecords are only used by a few glyphs, normally 431 # one. We encode these in format 1 again. 432 for key, glyphs in coverages.items(): 433 if key not in handled: 434 for g in glyphs: 435 st = buildSinglePosSubtable({g: values[key]}, glyphMap) 436 result.append(st) 437 438 # When the OpenType layout engine traverses the subtables, it will 439 # stop after the first matching subtable. Therefore, we sort the 440 # resulting subtables by decreasing coverage size; this increases 441 # the chance that the layout engine can do an early exit. (Of course, 442 # this would only be true if all glyphs were equally frequent, which 443 # is not really the case; but we do not know their distribution). 444 # If two subtables cover the same number of glyphs, we sort them 445 # by glyph ID so that our output is deterministic. 446 result.sort(key=lambda t: _getSinglePosTableKey(t, glyphMap)) 447 return result 448 449 450def buildSinglePosSubtable(values, glyphMap): 451 """{glyphName: otBase.ValueRecord} --> otTables.SinglePos""" 452 self = ot.SinglePos() 453 self.Coverage = buildCoverage(values.keys(), glyphMap) 454 valueRecords = [values[g] for g in self.Coverage.glyphs] 455 self.ValueFormat = 0 456 for v in valueRecords: 457 self.ValueFormat |= v.getFormat() 458 if all(v == valueRecords[0] for v in valueRecords): 459 self.Format = 1 460 if self.ValueFormat != 0: 461 self.Value = valueRecords[0] 462 else: 463 self.Value = None 464 else: 465 self.Format = 2 466 self.Value = valueRecords 467 self.ValueCount = len(self.Value) 468 return self 469 470 471def _getSinglePosTableKey(subtable, glyphMap): 472 assert isinstance(subtable, ot.SinglePos), subtable 473 glyphs = subtable.Coverage.glyphs 474 return (-len(glyphs), glyphMap[glyphs[0]]) 475 476 477def _getSinglePosValueKey(valueRecord): 478 """otBase.ValueRecord --> (2, ("YPlacement": 12))""" 479 assert isinstance(valueRecord, ValueRecord), valueRecord 480 valueFormat, result = 0, [] 481 for name, value in valueRecord.__dict__.items(): 482 if isinstance(value, ot.Device): 483 result.append((name, _makeDeviceTuple(value))) 484 else: 485 result.append((name, value)) 486 valueFormat |= valueRecordFormatDict[name][0] 487 result.sort() 488 result.insert(0, valueFormat) 489 return tuple(result) 490 491 492_DeviceTuple = namedtuple("_DeviceTuple", "DeltaFormat StartSize EndSize DeltaValue") 493 494 495def _makeDeviceTuple(device): 496 """otTables.Device --> tuple, for making device tables unique""" 497 return _DeviceTuple( 498 device.DeltaFormat, 499 device.StartSize, 500 device.EndSize, 501 () if device.DeltaFormat & 0x8000 else tuple(device.DeltaValue) 502 ) 503 504 505def _getSinglePosValueSize(valueKey): 506 """Returns how many ushorts this valueKey (short form of ValueRecord) takes up""" 507 count = 0 508 for _, v in valueKey[1:]: 509 if isinstance(v, _DeviceTuple): 510 count += len(v.DeltaValue) + 3 511 else: 512 count += 1 513 return count 514 515def buildValue(value): 516 self = ValueRecord() 517 for k, v in value.items(): 518 setattr(self, k, v) 519 return self 520 521 522# GDEF 523 524def buildAttachList(attachPoints, glyphMap): 525 """{"glyphName": [4, 23]} --> otTables.AttachList, or None""" 526 if not attachPoints: 527 return None 528 self = ot.AttachList() 529 self.Coverage = buildCoverage(attachPoints.keys(), glyphMap) 530 self.AttachPoint = [buildAttachPoint(attachPoints[g]) 531 for g in self.Coverage.glyphs] 532 self.GlyphCount = len(self.AttachPoint) 533 return self 534 535 536def buildAttachPoint(points): 537 """[4, 23, 41] --> otTables.AttachPoint""" 538 if not points: 539 return None 540 self = ot.AttachPoint() 541 self.PointIndex = sorted(set(points)) 542 self.PointCount = len(self.PointIndex) 543 return self 544 545 546def buildCaretValueForCoord(coord): 547 """500 --> otTables.CaretValue, format 1""" 548 self = ot.CaretValue() 549 self.Format = 1 550 self.Coordinate = coord 551 return self 552 553 554def buildCaretValueForPoint(point): 555 """4 --> otTables.CaretValue, format 2""" 556 self = ot.CaretValue() 557 self.Format = 2 558 self.CaretValuePoint = point 559 return self 560 561 562def buildLigCaretList(coords, points, glyphMap): 563 """{"f_f_i":[300,600]}, {"c_t":[28]} --> otTables.LigCaretList, or None""" 564 glyphs = set(coords.keys()) if coords else set() 565 if points: 566 glyphs.update(points.keys()) 567 carets = {g: buildLigGlyph(coords.get(g), points.get(g)) for g in glyphs} 568 carets = {g: c for g, c in carets.items() if c is not None} 569 if not carets: 570 return None 571 self = ot.LigCaretList() 572 self.Coverage = buildCoverage(carets.keys(), glyphMap) 573 self.LigGlyph = [carets[g] for g in self.Coverage.glyphs] 574 self.LigGlyphCount = len(self.LigGlyph) 575 return self 576 577 578def buildLigGlyph(coords, points): 579 """([500], [4]) --> otTables.LigGlyph; None for empty coords/points""" 580 carets = [] 581 if coords: 582 carets.extend([buildCaretValueForCoord(c) for c in sorted(coords)]) 583 if points: 584 carets.extend([buildCaretValueForPoint(p) for p in sorted(points)]) 585 if not carets: 586 return None 587 self = ot.LigGlyph() 588 self.CaretValue = carets 589 self.CaretCount = len(self.CaretValue) 590 return self 591 592 593def buildMarkGlyphSetsDef(markSets, glyphMap): 594 """[{"acute","grave"}, {"caron","grave"}] --> otTables.MarkGlyphSetsDef""" 595 if not markSets: 596 return None 597 self = ot.MarkGlyphSetsDef() 598 self.MarkSetTableFormat = 1 599 self.Coverage = [buildCoverage(m, glyphMap) for m in markSets] 600 self.MarkSetCount = len(self.Coverage) 601 return self 602 603 604class ClassDefBuilder(object): 605 """Helper for building ClassDef tables.""" 606 def __init__(self, useClass0): 607 self.classes_ = set() 608 self.glyphs_ = {} 609 self.useClass0_ = useClass0 610 611 def canAdd(self, glyphs): 612 if isinstance(glyphs, (set, frozenset)): 613 glyphs = sorted(glyphs) 614 glyphs = tuple(glyphs) 615 if glyphs in self.classes_: 616 return True 617 for glyph in glyphs: 618 if glyph in self.glyphs_: 619 return False 620 return True 621 622 def add(self, glyphs): 623 if isinstance(glyphs, (set, frozenset)): 624 glyphs = sorted(glyphs) 625 glyphs = tuple(glyphs) 626 if glyphs in self.classes_: 627 return 628 self.classes_.add(glyphs) 629 for glyph in glyphs: 630 assert glyph not in self.glyphs_ 631 self.glyphs_[glyph] = glyphs 632 633 def classes(self): 634 # In ClassDef1 tables, class id #0 does not need to be encoded 635 # because zero is the default. Therefore, we use id #0 for the 636 # glyph class that has the largest number of members. However, 637 # in other tables than ClassDef1, 0 means "every other glyph" 638 # so we should not use that ID for any real glyph classes; 639 # we implement this by inserting an empty set at position 0. 640 # 641 # TODO: Instead of counting the number of glyphs in each class, 642 # we should determine the encoded size. If the glyphs in a large 643 # class form a contiguous range, the encoding is actually quite 644 # compact, whereas a non-contiguous set might need a lot of bytes 645 # in the output file. We don't get this right with the key below. 646 result = sorted(self.classes_, key=lambda s: (len(s), s), reverse=True) 647 if not self.useClass0_: 648 result.insert(0, frozenset()) 649 return result 650 651 def build(self): 652 glyphClasses = {} 653 for classID, glyphs in enumerate(self.classes()): 654 if classID == 0: 655 continue 656 for glyph in glyphs: 657 glyphClasses[glyph] = classID 658 classDef = ot.ClassDef() 659 classDef.classDefs = glyphClasses 660 return classDef 661