Home | History | Annotate | Download | only in otlLib
      1 from __future__ import print_function, division, absolute_import
      2 from __future__ import unicode_literals
      3 from fontTools.misc.testTools import getXML
      4 from fontTools.otlLib import builder
      5 from fontTools.ttLib.tables import otTables
      6 from itertools import chain
      7 import unittest
      8 
      9 
     10 class BuilderTest(unittest.TestCase):
     11     GLYPHS = (".notdef space zero one two three four five six "
     12               "A B C a b c grave acute cedilla f_f_i f_i c_t").split()
     13     GLYPHMAP = {name: num for num, name in enumerate(GLYPHS)}
     14 
     15     ANCHOR1 = builder.buildAnchor(11, -11)
     16     ANCHOR2 = builder.buildAnchor(22, -22)
     17     ANCHOR3 = builder.buildAnchor(33, -33)
     18 
     19     def __init__(self, methodName):
     20         unittest.TestCase.__init__(self, methodName)
     21         # Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
     22         # and fires deprecation warnings if a program uses the old name.
     23         if not hasattr(self, "assertRaisesRegex"):
     24             self.assertRaisesRegex = self.assertRaisesRegexp
     25 
     26     @classmethod
     27     def setUpClass(cls):
     28         cls.maxDiff = None
     29 
     30     def test_buildAnchor_format1(self):
     31         anchor = builder.buildAnchor(23, 42)
     32         self.assertEqual(getXML(anchor.toXML),
     33                          ['<Anchor Format="1">',
     34                           '  <XCoordinate value="23"/>',
     35                           '  <YCoordinate value="42"/>',
     36                           '</Anchor>'])
     37 
     38     def test_buildAnchor_format2(self):
     39         anchor = builder.buildAnchor(23, 42, point=17)
     40         self.assertEqual(getXML(anchor.toXML),
     41                          ['<Anchor Format="2">',
     42                           '  <XCoordinate value="23"/>',
     43                           '  <YCoordinate value="42"/>',
     44                           '  <AnchorPoint value="17"/>',
     45                           '</Anchor>'])
     46 
     47     def test_buildAnchor_format3(self):
     48         anchor = builder.buildAnchor(
     49             23, 42,
     50             deviceX=builder.buildDevice({1: 1, 0: 0}),
     51             deviceY=builder.buildDevice({7: 7}))
     52         self.assertEqual(getXML(anchor.toXML),
     53                          ['<Anchor Format="3">',
     54                           '  <XCoordinate value="23"/>',
     55                           '  <YCoordinate value="42"/>',
     56                           '  <XDeviceTable>',
     57                           '    <StartSize value="0"/>',
     58                           '    <EndSize value="1"/>',
     59                           '    <DeltaFormat value="1"/>',
     60                           '    <DeltaValue value="[0, 1]"/>',
     61                           '  </XDeviceTable>',
     62                           '  <YDeviceTable>',
     63                           '    <StartSize value="7"/>',
     64                           '    <EndSize value="7"/>',
     65                           '    <DeltaFormat value="2"/>',
     66                           '    <DeltaValue value="[7]"/>',
     67                           '  </YDeviceTable>',
     68                           '</Anchor>'])
     69 
     70     def test_buildAttachList(self):
     71         attachList = builder.buildAttachList({
     72             "zero": [23, 7], "one": [1],
     73         }, self.GLYPHMAP)
     74         self.assertEqual(getXML(attachList.toXML),
     75                          ['<AttachList>',
     76                           '  <Coverage>',
     77                           '    <Glyph value="zero"/>',
     78                           '    <Glyph value="one"/>',
     79                           '  </Coverage>',
     80                           '  <!-- GlyphCount=2 -->',
     81                           '  <AttachPoint index="0">',
     82                           '    <!-- PointCount=2 -->',
     83                           '    <PointIndex index="0" value="7"/>',
     84                           '    <PointIndex index="1" value="23"/>',
     85                           '  </AttachPoint>',
     86                           '  <AttachPoint index="1">',
     87                           '    <!-- PointCount=1 -->',
     88                           '    <PointIndex index="0" value="1"/>',
     89                           '  </AttachPoint>',
     90                           '</AttachList>'])
     91 
     92     def test_buildAttachList_empty(self):
     93         self.assertIsNone(builder.buildAttachList({}, self.GLYPHMAP))
     94 
     95     def test_buildAttachPoint(self):
     96         attachPoint = builder.buildAttachPoint([7, 3])
     97         self.assertEqual(getXML(attachPoint.toXML),
     98                          ['<AttachPoint>',
     99                           '  <!-- PointCount=2 -->',
    100                           '  <PointIndex index="0" value="3"/>',
    101                           '  <PointIndex index="1" value="7"/>',
    102                           '</AttachPoint>'])
    103 
    104     def test_buildAttachPoint_empty(self):
    105         self.assertIsNone(builder.buildAttachPoint([]))
    106 
    107     def test_buildAttachPoint_duplicate(self):
    108         attachPoint = builder.buildAttachPoint([7, 3, 7])
    109         self.assertEqual(getXML(attachPoint.toXML),
    110                          ['<AttachPoint>',
    111                           '  <!-- PointCount=2 -->',
    112                           '  <PointIndex index="0" value="3"/>',
    113                           '  <PointIndex index="1" value="7"/>',
    114                           '</AttachPoint>'])
    115 
    116 
    117     def test_buildBaseArray(self):
    118         anchor = builder.buildAnchor
    119         baseArray = builder.buildBaseArray({
    120             "a": {2: anchor(300, 80)},
    121             "c": {1: anchor(300, 80), 2: anchor(300, -20)}
    122         }, numMarkClasses=4, glyphMap=self.GLYPHMAP)
    123         self.assertEqual(getXML(baseArray.toXML),
    124                          ['<BaseArray>',
    125                           '  <!-- BaseCount=2 -->',
    126                           '  <BaseRecord index="0">',
    127                           '    <BaseAnchor index="0" empty="1"/>',
    128                           '    <BaseAnchor index="1" empty="1"/>',
    129                           '    <BaseAnchor index="2" Format="1">',
    130                           '      <XCoordinate value="300"/>',
    131                           '      <YCoordinate value="80"/>',
    132                           '    </BaseAnchor>',
    133                           '    <BaseAnchor index="3" empty="1"/>',
    134                           '  </BaseRecord>',
    135                           '  <BaseRecord index="1">',
    136                           '    <BaseAnchor index="0" empty="1"/>',
    137                           '    <BaseAnchor index="1" Format="1">',
    138                           '      <XCoordinate value="300"/>',
    139                           '      <YCoordinate value="80"/>',
    140                           '    </BaseAnchor>',
    141                           '    <BaseAnchor index="2" Format="1">',
    142                           '      <XCoordinate value="300"/>',
    143                           '      <YCoordinate value="-20"/>',
    144                           '    </BaseAnchor>',
    145                           '    <BaseAnchor index="3" empty="1"/>',
    146                           '  </BaseRecord>',
    147                           '</BaseArray>'])
    148 
    149     def test_buildBaseRecord(self):
    150         a = builder.buildAnchor
    151         rec = builder.buildBaseRecord([a(500, -20), None, a(300, -15)])
    152         self.assertEqual(getXML(rec.toXML),
    153                          ['<BaseRecord>',
    154                           '  <BaseAnchor index="0" Format="1">',
    155                           '    <XCoordinate value="500"/>',
    156                           '    <YCoordinate value="-20"/>',
    157                           '  </BaseAnchor>',
    158                           '  <BaseAnchor index="1" empty="1"/>',
    159                           '  <BaseAnchor index="2" Format="1">',
    160                           '    <XCoordinate value="300"/>',
    161                           '    <YCoordinate value="-15"/>',
    162                           '  </BaseAnchor>',
    163                           '</BaseRecord>'])
    164 
    165     def test_buildCaretValueForCoord(self):
    166         caret = builder.buildCaretValueForCoord(500)
    167         self.assertEqual(getXML(caret.toXML),
    168                          ['<CaretValue Format="1">',
    169                           '  <Coordinate value="500"/>',
    170                           '</CaretValue>'])
    171 
    172     def test_buildCaretValueForPoint(self):
    173         caret = builder.buildCaretValueForPoint(23)
    174         self.assertEqual(getXML(caret.toXML),
    175                          ['<CaretValue Format="2">',
    176                           '  <CaretValuePoint value="23"/>',
    177                           '</CaretValue>'])
    178 
    179     def test_buildComponentRecord(self):
    180         a = builder.buildAnchor
    181         rec = builder.buildComponentRecord([a(500, -20), None, a(300, -15)])
    182         self.assertEqual(getXML(rec.toXML),
    183                          ['<ComponentRecord>',
    184                           '  <LigatureAnchor index="0" Format="1">',
    185                           '    <XCoordinate value="500"/>',
    186                           '    <YCoordinate value="-20"/>',
    187                           '  </LigatureAnchor>',
    188                           '  <LigatureAnchor index="1" empty="1"/>',
    189                           '  <LigatureAnchor index="2" Format="1">',
    190                           '    <XCoordinate value="300"/>',
    191                           '    <YCoordinate value="-15"/>',
    192                           '  </LigatureAnchor>',
    193                           '</ComponentRecord>'])
    194 
    195     def test_buildComponentRecord_empty(self):
    196         self.assertIsNone(builder.buildComponentRecord([]))
    197 
    198     def test_buildComponentRecord_None(self):
    199         self.assertIsNone(builder.buildComponentRecord(None))
    200 
    201     def test_buildCoverage(self):
    202         cov = builder.buildCoverage({"two", "four"}, {"two": 2, "four": 4})
    203         self.assertEqual(getXML(cov.toXML),
    204                          ['<Coverage>',
    205                           '  <Glyph value="two"/>',
    206                           '  <Glyph value="four"/>',
    207                           '</Coverage>'])
    208 
    209     def test_buildCursivePos(self):
    210         pos = builder.buildCursivePosSubtable({
    211             "two": (self.ANCHOR1, self.ANCHOR2),
    212             "four": (self.ANCHOR3, self.ANCHOR1)
    213         }, self.GLYPHMAP)
    214         self.assertEqual(getXML(pos.toXML),
    215                          ['<CursivePos Format="1">',
    216                           '  <Coverage>',
    217                           '    <Glyph value="two"/>',
    218                           '    <Glyph value="four"/>',
    219                           '  </Coverage>',
    220                           '  <!-- EntryExitCount=2 -->',
    221                           '  <EntryExitRecord index="0">',
    222                           '    <EntryAnchor Format="1">',
    223                           '      <XCoordinate value="11"/>',
    224                           '      <YCoordinate value="-11"/>',
    225                           '    </EntryAnchor>',
    226                           '    <ExitAnchor Format="1">',
    227                           '      <XCoordinate value="22"/>',
    228                           '      <YCoordinate value="-22"/>',
    229                           '    </ExitAnchor>',
    230                           '  </EntryExitRecord>',
    231                           '  <EntryExitRecord index="1">',
    232                           '    <EntryAnchor Format="1">',
    233                           '      <XCoordinate value="33"/>',
    234                           '      <YCoordinate value="-33"/>',
    235                           '    </EntryAnchor>',
    236                           '    <ExitAnchor Format="1">',
    237                           '      <XCoordinate value="11"/>',
    238                           '      <YCoordinate value="-11"/>',
    239                           '    </ExitAnchor>',
    240                           '  </EntryExitRecord>',
    241                           '</CursivePos>'])
    242 
    243     def test_buildDevice_format1(self):
    244         device = builder.buildDevice({1:1, 0:0})
    245         self.assertEqual(getXML(device.toXML),
    246                          ['<Device>',
    247                           '  <StartSize value="0"/>',
    248                           '  <EndSize value="1"/>',
    249                           '  <DeltaFormat value="1"/>',
    250                           '  <DeltaValue value="[0, 1]"/>',
    251                           '</Device>'])
    252 
    253     def test_buildDevice_format2(self):
    254         device = builder.buildDevice({2:2, 0:1, 1:0})
    255         self.assertEqual(getXML(device.toXML),
    256                          ['<Device>',
    257                           '  <StartSize value="0"/>',
    258                           '  <EndSize value="2"/>',
    259                           '  <DeltaFormat value="2"/>',
    260                           '  <DeltaValue value="[1, 0, 2]"/>',
    261                           '</Device>'])
    262 
    263     def test_buildDevice_format3(self):
    264         device = builder.buildDevice({5:3, 1:77})
    265         self.assertEqual(getXML(device.toXML),
    266                          ['<Device>',
    267                           '  <StartSize value="1"/>',
    268                           '  <EndSize value="5"/>',
    269                           '  <DeltaFormat value="3"/>',
    270                           '  <DeltaValue value="[77, 0, 0, 0, 3]"/>',
    271                           '</Device>'])
    272 
    273     def test_buildLigatureArray(self):
    274         anchor = builder.buildAnchor
    275         ligatureArray = builder.buildLigatureArray({
    276             "f_i": [{2: anchor(300, -20)}, {}],
    277             "c_t": [{}, {1: anchor(500, 350), 2: anchor(1300, -20)}]
    278         }, numMarkClasses=4, glyphMap=self.GLYPHMAP)
    279         self.assertEqual(getXML(ligatureArray.toXML),
    280                          ['<LigatureArray>',
    281                           '  <!-- LigatureCount=2 -->',
    282                           '  <LigatureAttach index="0">',  # f_i
    283                           '    <!-- ComponentCount=2 -->',
    284                           '    <ComponentRecord index="0">',
    285                           '      <LigatureAnchor index="0" empty="1"/>',
    286                           '      <LigatureAnchor index="1" empty="1"/>',
    287                           '      <LigatureAnchor index="2" Format="1">',
    288                           '        <XCoordinate value="300"/>',
    289                           '        <YCoordinate value="-20"/>',
    290                           '      </LigatureAnchor>',
    291                           '      <LigatureAnchor index="3" empty="1"/>',
    292                           '    </ComponentRecord>',
    293                           '    <ComponentRecord index="1">',
    294                           '      <LigatureAnchor index="0" empty="1"/>',
    295                           '      <LigatureAnchor index="1" empty="1"/>',
    296                           '      <LigatureAnchor index="2" empty="1"/>',
    297                           '      <LigatureAnchor index="3" empty="1"/>',
    298                           '    </ComponentRecord>',
    299                           '  </LigatureAttach>',
    300                           '  <LigatureAttach index="1">',
    301                           '    <!-- ComponentCount=2 -->',
    302                           '    <ComponentRecord index="0">',
    303                           '      <LigatureAnchor index="0" empty="1"/>',
    304                           '      <LigatureAnchor index="1" empty="1"/>',
    305                           '      <LigatureAnchor index="2" empty="1"/>',
    306                           '      <LigatureAnchor index="3" empty="1"/>',
    307                           '    </ComponentRecord>',
    308                           '    <ComponentRecord index="1">',
    309                           '      <LigatureAnchor index="0" empty="1"/>',
    310                           '      <LigatureAnchor index="1" Format="1">',
    311                           '        <XCoordinate value="500"/>',
    312                           '        <YCoordinate value="350"/>',
    313                           '      </LigatureAnchor>',
    314                           '      <LigatureAnchor index="2" Format="1">',
    315                           '        <XCoordinate value="1300"/>',
    316                           '        <YCoordinate value="-20"/>',
    317                           '      </LigatureAnchor>',
    318                           '      <LigatureAnchor index="3" empty="1"/>',
    319                           '    </ComponentRecord>',
    320                           '  </LigatureAttach>',
    321                           '</LigatureArray>'])
    322 
    323     def test_buildLigatureAttach(self):
    324         anchor = builder.buildAnchor
    325         attach = builder.buildLigatureAttach([
    326             [anchor(500, -10), None],
    327             [None, anchor(300, -20), None]])
    328         self.assertEqual(getXML(attach.toXML),
    329                          ['<LigatureAttach>',
    330                           '  <!-- ComponentCount=2 -->',
    331                           '  <ComponentRecord index="0">',
    332                           '    <LigatureAnchor index="0" Format="1">',
    333                           '      <XCoordinate value="500"/>',
    334                           '      <YCoordinate value="-10"/>',
    335                           '    </LigatureAnchor>',
    336                           '    <LigatureAnchor index="1" empty="1"/>',
    337                           '  </ComponentRecord>',
    338                           '  <ComponentRecord index="1">',
    339                           '    <LigatureAnchor index="0" empty="1"/>',
    340                           '    <LigatureAnchor index="1" Format="1">',
    341                           '      <XCoordinate value="300"/>',
    342                           '      <YCoordinate value="-20"/>',
    343                           '    </LigatureAnchor>',
    344                           '    <LigatureAnchor index="2" empty="1"/>',
    345                           '  </ComponentRecord>',
    346                           '</LigatureAttach>'])
    347 
    348     def test_buildLigatureAttach_emptyComponents(self):
    349         attach = builder.buildLigatureAttach([[], None])
    350         self.assertEqual(getXML(attach.toXML),
    351                          ['<LigatureAttach>',
    352                           '  <!-- ComponentCount=2 -->',
    353                           '  <ComponentRecord index="0" empty="1"/>',
    354                           '  <ComponentRecord index="1" empty="1"/>',
    355                           '</LigatureAttach>'])
    356 
    357     def test_buildLigatureAttach_noComponents(self):
    358         attach = builder.buildLigatureAttach([])
    359         self.assertEqual(getXML(attach.toXML),
    360                          ['<LigatureAttach>',
    361                           '  <!-- ComponentCount=0 -->',
    362                           '</LigatureAttach>'])
    363 
    364     def test_buildLigCaretList(self):
    365         carets = builder.buildLigCaretList(
    366             {"f_f_i": [300, 600]}, {"c_t": [42]}, self.GLYPHMAP)
    367         self.assertEqual(getXML(carets.toXML),
    368                          ['<LigCaretList>',
    369                           '  <Coverage>',
    370                           '    <Glyph value="f_f_i"/>',
    371                           '    <Glyph value="c_t"/>',
    372                           '  </Coverage>',
    373                           '  <!-- LigGlyphCount=2 -->',
    374                           '  <LigGlyph index="0">',
    375                           '    <!-- CaretCount=2 -->',
    376                           '    <CaretValue index="0" Format="1">',
    377                           '      <Coordinate value="300"/>',
    378                           '    </CaretValue>',
    379                           '    <CaretValue index="1" Format="1">',
    380                           '      <Coordinate value="600"/>',
    381                           '    </CaretValue>',
    382                           '  </LigGlyph>',
    383                           '  <LigGlyph index="1">',
    384                           '    <!-- CaretCount=1 -->',
    385                           '    <CaretValue index="0" Format="2">',
    386                           '      <CaretValuePoint value="42"/>',
    387                           '    </CaretValue>',
    388                           '  </LigGlyph>',
    389                           '</LigCaretList>'])
    390 
    391     def test_buildLigCaretList_bothCoordsAndPointsForSameGlyph(self):
    392         carets = builder.buildLigCaretList(
    393             {"f_f_i": [300]}, {"f_f_i": [7]}, self.GLYPHMAP)
    394         self.assertEqual(getXML(carets.toXML),
    395                          ['<LigCaretList>',
    396                           '  <Coverage>',
    397                           '    <Glyph value="f_f_i"/>',
    398                           '  </Coverage>',
    399                           '  <!-- LigGlyphCount=1 -->',
    400                           '  <LigGlyph index="0">',
    401                           '    <!-- CaretCount=2 -->',
    402                           '    <CaretValue index="0" Format="1">',
    403                           '      <Coordinate value="300"/>',
    404                           '    </CaretValue>',
    405                           '    <CaretValue index="1" Format="2">',
    406                           '      <CaretValuePoint value="7"/>',
    407                           '    </CaretValue>',
    408                           '  </LigGlyph>',
    409                           '</LigCaretList>'])
    410 
    411     def test_buildLigCaretList_empty(self):
    412         self.assertIsNone(builder.buildLigCaretList({}, {}, self.GLYPHMAP))
    413 
    414     def test_buildLigCaretList_None(self):
    415         self.assertIsNone(builder.buildLigCaretList(None, None, self.GLYPHMAP))
    416 
    417     def test_buildLigGlyph_coords(self):
    418         lig = builder.buildLigGlyph([500, 800], None)
    419         self.assertEqual(getXML(lig.toXML),
    420                          ['<LigGlyph>',
    421                           '  <!-- CaretCount=2 -->',
    422                           '  <CaretValue index="0" Format="1">',
    423                           '    <Coordinate value="500"/>',
    424                           '  </CaretValue>',
    425                           '  <CaretValue index="1" Format="1">',
    426                           '    <Coordinate value="800"/>',
    427                           '  </CaretValue>',
    428                           '</LigGlyph>'])
    429 
    430     def test_buildLigGlyph_empty(self):
    431         self.assertIsNone(builder.buildLigGlyph([], []))
    432 
    433     def test_buildLigGlyph_None(self):
    434         self.assertIsNone(builder.buildLigGlyph(None, None))
    435 
    436     def test_buildLigGlyph_points(self):
    437         lig = builder.buildLigGlyph(None, [2])
    438         self.assertEqual(getXML(lig.toXML),
    439                          ['<LigGlyph>',
    440                           '  <!-- CaretCount=1 -->',
    441                           '  <CaretValue index="0" Format="2">',
    442                           '    <CaretValuePoint value="2"/>',
    443                           '  </CaretValue>',
    444                           '</LigGlyph>'])
    445 
    446     def test_buildLookup(self):
    447         s1 = builder.buildSingleSubstSubtable({"one": "two"})
    448         s2 = builder.buildSingleSubstSubtable({"three": "four"})
    449         lookup = builder.buildLookup([s1, s2], flags=7)
    450         self.assertEqual(getXML(lookup.toXML),
    451                          ['<Lookup>',
    452                           '  <LookupType value="1"/>',
    453                           '  <LookupFlag value="7"/>',
    454                           '  <!-- SubTableCount=2 -->',
    455                           '  <SingleSubst index="0">',
    456                           '    <Substitution in="one" out="two"/>',
    457                           '  </SingleSubst>',
    458                           '  <SingleSubst index="1">',
    459                           '    <Substitution in="three" out="four"/>',
    460                           '  </SingleSubst>',
    461                           '</Lookup>'])
    462 
    463     def test_buildLookup_badFlags(self):
    464         s = builder.buildSingleSubstSubtable({"one": "two"})
    465         self.assertRaisesRegex(
    466             AssertionError, "if markFilterSet is None, "
    467             "flags must not set LOOKUP_FLAG_USE_MARK_FILTERING_SET; "
    468             "flags=0x0010",
    469             builder.buildLookup, [s],
    470             builder.LOOKUP_FLAG_USE_MARK_FILTERING_SET, None)
    471         self.assertRaisesRegex(
    472             AssertionError, "if markFilterSet is not None, "
    473             "flags must set LOOKUP_FLAG_USE_MARK_FILTERING_SET; "
    474             "flags=0x0004",
    475             builder.buildLookup, [s],
    476             builder.LOOKUP_FLAG_IGNORE_LIGATURES, 777)
    477 
    478     def test_buildLookup_conflictingSubtableTypes(self):
    479         s1 = builder.buildSingleSubstSubtable({"one": "two"})
    480         s2 = builder.buildAlternateSubstSubtable({"one": ["two", "three"]})
    481         self.assertRaisesRegex(
    482             AssertionError, "all subtables must have the same LookupType",
    483             builder.buildLookup, [s1, s2])
    484 
    485     def test_buildLookup_noSubtables(self):
    486         self.assertIsNone(builder.buildLookup([]))
    487         self.assertIsNone(builder.buildLookup(None))
    488         self.assertIsNone(builder.buildLookup([None]))
    489         self.assertIsNone(builder.buildLookup([None, None]))
    490 
    491     def test_buildLookup_markFilterSet(self):
    492         s = builder.buildSingleSubstSubtable({"one": "two"})
    493         flags = (builder.LOOKUP_FLAG_RIGHT_TO_LEFT |
    494                  builder.LOOKUP_FLAG_USE_MARK_FILTERING_SET)
    495         lookup = builder.buildLookup([s], flags, markFilterSet=999)
    496         self.assertEqual(getXML(lookup.toXML),
    497                          ['<Lookup>',
    498                           '  <LookupType value="1"/>',
    499                           '  <LookupFlag value="17"/>',
    500                           '  <!-- SubTableCount=1 -->',
    501                           '  <SingleSubst index="0">',
    502                           '    <Substitution in="one" out="two"/>',
    503                           '  </SingleSubst>',
    504                           '  <MarkFilteringSet value="999"/>',
    505                           '</Lookup>'])
    506 
    507     def test_buildMarkArray(self):
    508         markArray = builder.buildMarkArray({
    509             "acute": (7, builder.buildAnchor(300, 800)),
    510             "grave": (2, builder.buildAnchor(10, 80))
    511         }, self.GLYPHMAP)
    512         self.assertLess(self.GLYPHMAP["grave"], self.GLYPHMAP["acute"])
    513         self.assertEqual(getXML(markArray.toXML),
    514                          ['<MarkArray>',
    515                           '  <!-- MarkCount=2 -->',
    516                           '  <MarkRecord index="0">',
    517                           '    <Class value="2"/>',
    518                           '    <MarkAnchor Format="1">',
    519                           '      <XCoordinate value="10"/>',
    520                           '      <YCoordinate value="80"/>',
    521                           '    </MarkAnchor>',
    522                           '  </MarkRecord>',
    523                           '  <MarkRecord index="1">',
    524                           '    <Class value="7"/>',
    525                           '    <MarkAnchor Format="1">',
    526                           '      <XCoordinate value="300"/>',
    527                           '      <YCoordinate value="800"/>',
    528                           '    </MarkAnchor>',
    529                           '  </MarkRecord>',
    530                           '</MarkArray>'])
    531 
    532     def test_buildMarkBasePosSubtable(self):
    533         anchor = builder.buildAnchor
    534         marks = {
    535             "acute": (0, anchor(300, 700)),
    536             "cedilla": (1, anchor(300, -100)),
    537             "grave": (0, anchor(300, 700))
    538         }
    539         bases = {
    540             # Make sure we can handle missing entries.
    541             "A": {},  # no entry for any markClass
    542             "B": {0: anchor(500, 900)},  # only markClass 0 specified
    543             "C": {1: anchor(500, -10)},  # only markClass 1 specified
    544 
    545             "a": {0: anchor(500, 400), 1: anchor(500, -20)},
    546             "b": {0: anchor(500, 800), 1: anchor(500, -20)}
    547         }
    548         table = builder.buildMarkBasePosSubtable(marks, bases, self.GLYPHMAP)
    549         self.assertEqual(getXML(table.toXML),
    550                          ['<MarkBasePos Format="1">',
    551                           '  <MarkCoverage>',
    552                           '    <Glyph value="grave"/>',
    553                           '    <Glyph value="acute"/>',
    554                           '    <Glyph value="cedilla"/>',
    555                           '  </MarkCoverage>',
    556                           '  <BaseCoverage>',
    557                           '    <Glyph value="A"/>',
    558                           '    <Glyph value="B"/>',
    559                           '    <Glyph value="C"/>',
    560                           '    <Glyph value="a"/>',
    561                           '    <Glyph value="b"/>',
    562                           '  </BaseCoverage>',
    563                           '  <!-- ClassCount=2 -->',
    564                           '  <MarkArray>',
    565                           '    <!-- MarkCount=3 -->',
    566                           '    <MarkRecord index="0">',  # grave
    567                           '      <Class value="0"/>',
    568                           '      <MarkAnchor Format="1">',
    569                           '        <XCoordinate value="300"/>',
    570                           '        <YCoordinate value="700"/>',
    571                           '      </MarkAnchor>',
    572                           '    </MarkRecord>',
    573                           '    <MarkRecord index="1">',  # acute
    574                           '      <Class value="0"/>',
    575                           '      <MarkAnchor Format="1">',
    576                           '        <XCoordinate value="300"/>',
    577                           '        <YCoordinate value="700"/>',
    578                           '      </MarkAnchor>',
    579                           '    </MarkRecord>',
    580                           '    <MarkRecord index="2">',  # cedilla
    581                           '      <Class value="1"/>',
    582                           '      <MarkAnchor Format="1">',
    583                           '        <XCoordinate value="300"/>',
    584                           '        <YCoordinate value="-100"/>',
    585                           '      </MarkAnchor>',
    586                           '    </MarkRecord>',
    587                           '  </MarkArray>',
    588                           '  <BaseArray>',
    589                           '    <!-- BaseCount=5 -->',
    590                           '    <BaseRecord index="0">',  # A
    591                           '      <BaseAnchor index="0" empty="1"/>',
    592                           '      <BaseAnchor index="1" empty="1"/>',
    593                           '    </BaseRecord>',
    594                           '    <BaseRecord index="1">',  # B
    595                           '      <BaseAnchor index="0" Format="1">',
    596                           '        <XCoordinate value="500"/>',
    597                           '        <YCoordinate value="900"/>',
    598                           '      </BaseAnchor>',
    599                           '      <BaseAnchor index="1" empty="1"/>',
    600                           '    </BaseRecord>',
    601                           '    <BaseRecord index="2">',  # C
    602                           '      <BaseAnchor index="0" empty="1"/>',
    603                           '      <BaseAnchor index="1" Format="1">',
    604                           '        <XCoordinate value="500"/>',
    605                           '        <YCoordinate value="-10"/>',
    606                           '      </BaseAnchor>',
    607                           '    </BaseRecord>',
    608                           '    <BaseRecord index="3">',  # a
    609                           '      <BaseAnchor index="0" Format="1">',
    610                           '        <XCoordinate value="500"/>',
    611                           '        <YCoordinate value="400"/>',
    612                           '      </BaseAnchor>',
    613                           '      <BaseAnchor index="1" Format="1">',
    614                           '        <XCoordinate value="500"/>',
    615                           '        <YCoordinate value="-20"/>',
    616                           '      </BaseAnchor>',
    617                           '    </BaseRecord>',
    618                           '    <BaseRecord index="4">',  # b
    619                           '      <BaseAnchor index="0" Format="1">',
    620                           '        <XCoordinate value="500"/>',
    621                           '        <YCoordinate value="800"/>',
    622                           '      </BaseAnchor>',
    623                           '      <BaseAnchor index="1" Format="1">',
    624                           '        <XCoordinate value="500"/>',
    625                           '        <YCoordinate value="-20"/>',
    626                           '      </BaseAnchor>',
    627                           '    </BaseRecord>',
    628                           '  </BaseArray>',
    629                           '</MarkBasePos>'])
    630 
    631     def test_buildMarkGlyphSetsDef(self):
    632         marksets = builder.buildMarkGlyphSetsDef(
    633             [{"acute", "grave"}, {"cedilla", "grave"}], self.GLYPHMAP)
    634         self.assertEqual(getXML(marksets.toXML),
    635                          ['<MarkGlyphSetsDef>',
    636                           '  <MarkSetTableFormat value="1"/>',
    637                           '  <!-- MarkSetCount=2 -->',
    638                           '  <Coverage index="0">',
    639                           '    <Glyph value="grave"/>',
    640                           '    <Glyph value="acute"/>',
    641                           '  </Coverage>',
    642                           '  <Coverage index="1">',
    643                           '    <Glyph value="grave"/>',
    644                           '    <Glyph value="cedilla"/>',
    645                           '  </Coverage>',
    646                           '</MarkGlyphSetsDef>'])
    647 
    648     def test_buildMarkGlyphSetsDef_empty(self):
    649         self.assertIsNone(builder.buildMarkGlyphSetsDef([], self.GLYPHMAP))
    650 
    651     def test_buildMarkGlyphSetsDef_None(self):
    652         self.assertIsNone(builder.buildMarkGlyphSetsDef(None, self.GLYPHMAP))
    653 
    654     def test_buildMarkLigPosSubtable(self):
    655         anchor = builder.buildAnchor
    656         marks = {
    657             "acute": (0, anchor(300, 700)),
    658             "cedilla": (1, anchor(300, -100)),
    659             "grave": (0, anchor(300, 700))
    660         }
    661         bases = {
    662             "f_i": [{}, {0: anchor(200, 400)}],  # nothing on f; only 1 on i
    663             "c_t": [
    664                 {0: anchor(500, 600), 1: anchor(500, -20)},   # c
    665                 {0: anchor(1300, 800), 1: anchor(1300, -20)}  # t
    666             ]
    667         }
    668         table = builder.buildMarkLigPosSubtable(marks, bases, self.GLYPHMAP)
    669         self.assertEqual(getXML(table.toXML),
    670                          ['<MarkLigPos Format="1">',
    671                           '  <MarkCoverage>',
    672                           '    <Glyph value="grave"/>',
    673                           '    <Glyph value="acute"/>',
    674                           '    <Glyph value="cedilla"/>',
    675                           '  </MarkCoverage>',
    676                           '  <LigatureCoverage>',
    677                           '    <Glyph value="f_i"/>',
    678                           '    <Glyph value="c_t"/>',
    679                           '  </LigatureCoverage>',
    680                           '  <!-- ClassCount=2 -->',
    681                           '  <MarkArray>',
    682                           '    <!-- MarkCount=3 -->',
    683                           '    <MarkRecord index="0">',
    684                           '      <Class value="0"/>',
    685                           '      <MarkAnchor Format="1">',
    686                           '        <XCoordinate value="300"/>',
    687                           '        <YCoordinate value="700"/>',
    688                           '      </MarkAnchor>',
    689                           '    </MarkRecord>',
    690                           '    <MarkRecord index="1">',
    691                           '      <Class value="0"/>',
    692                           '      <MarkAnchor Format="1">',
    693                           '        <XCoordinate value="300"/>',
    694                           '        <YCoordinate value="700"/>',
    695                           '      </MarkAnchor>',
    696                           '    </MarkRecord>',
    697                           '    <MarkRecord index="2">',
    698                           '      <Class value="1"/>',
    699                           '      <MarkAnchor Format="1">',
    700                           '        <XCoordinate value="300"/>',
    701                           '        <YCoordinate value="-100"/>',
    702                           '      </MarkAnchor>',
    703                           '    </MarkRecord>',
    704                           '  </MarkArray>',
    705                           '  <LigatureArray>',
    706                           '    <!-- LigatureCount=2 -->',
    707                           '    <LigatureAttach index="0">',
    708                           '      <!-- ComponentCount=2 -->',
    709                           '      <ComponentRecord index="0">',
    710                           '        <LigatureAnchor index="0" empty="1"/>',
    711                           '        <LigatureAnchor index="1" empty="1"/>',
    712                           '      </ComponentRecord>',
    713                           '      <ComponentRecord index="1">',
    714                           '        <LigatureAnchor index="0" Format="1">',
    715                           '          <XCoordinate value="200"/>',
    716                           '          <YCoordinate value="400"/>',
    717                           '        </LigatureAnchor>',
    718                           '        <LigatureAnchor index="1" empty="1"/>',
    719                           '      </ComponentRecord>',
    720                           '    </LigatureAttach>',
    721                           '    <LigatureAttach index="1">',
    722                           '      <!-- ComponentCount=2 -->',
    723                           '      <ComponentRecord index="0">',
    724                           '        <LigatureAnchor index="0" Format="1">',
    725                           '          <XCoordinate value="500"/>',
    726                           '          <YCoordinate value="600"/>',
    727                           '        </LigatureAnchor>',
    728                           '        <LigatureAnchor index="1" Format="1">',
    729                           '          <XCoordinate value="500"/>',
    730                           '          <YCoordinate value="-20"/>',
    731                           '        </LigatureAnchor>',
    732                           '      </ComponentRecord>',
    733                           '      <ComponentRecord index="1">',
    734                           '        <LigatureAnchor index="0" Format="1">',
    735                           '          <XCoordinate value="1300"/>',
    736                           '          <YCoordinate value="800"/>',
    737                           '        </LigatureAnchor>',
    738                           '        <LigatureAnchor index="1" Format="1">',
    739                           '          <XCoordinate value="1300"/>',
    740                           '          <YCoordinate value="-20"/>',
    741                           '        </LigatureAnchor>',
    742                           '      </ComponentRecord>',
    743                           '    </LigatureAttach>',
    744                           '  </LigatureArray>',
    745                           '</MarkLigPos>'])
    746 
    747     def test_buildMarkRecord(self):
    748         rec = builder.buildMarkRecord(17, builder.buildAnchor(500, -20))
    749         self.assertEqual(getXML(rec.toXML),
    750                          ['<MarkRecord>',
    751                           '  <Class value="17"/>',
    752                           '  <MarkAnchor Format="1">',
    753                           '    <XCoordinate value="500"/>',
    754                           '    <YCoordinate value="-20"/>',
    755                           '  </MarkAnchor>',
    756                           '</MarkRecord>'])
    757 
    758     def test_buildMark2Record(self):
    759         a = builder.buildAnchor
    760         rec = builder.buildMark2Record([a(500, -20), None, a(300, -15)])
    761         self.assertEqual(getXML(rec.toXML),
    762                          ['<Mark2Record>',
    763                           '  <Mark2Anchor index="0" Format="1">',
    764                           '    <XCoordinate value="500"/>',
    765                           '    <YCoordinate value="-20"/>',
    766                           '  </Mark2Anchor>',
    767                           '  <Mark2Anchor index="1" empty="1"/>',
    768                           '  <Mark2Anchor index="2" Format="1">',
    769                           '    <XCoordinate value="300"/>',
    770                           '    <YCoordinate value="-15"/>',
    771                           '  </Mark2Anchor>',
    772                           '</Mark2Record>'])
    773 
    774     def test_buildPairPosClassesSubtable(self):
    775         d20 = builder.buildValue({"XPlacement": -20})
    776         d50 = builder.buildValue({"XPlacement": -50})
    777         d0 = builder.buildValue({})
    778         d8020 = builder.buildValue({"XPlacement": -80, "YPlacement": -20})
    779         subtable = builder.buildPairPosClassesSubtable({
    780             (tuple("A",), tuple(["zero"])): (d0, d50),
    781             (tuple("A",), tuple(["one", "two"])):  (None, d20),
    782             (tuple(["B", "C"]), tuple(["zero"])): (d8020, d50),
    783         }, self.GLYPHMAP)
    784         self.assertEqual(getXML(subtable.toXML),
    785                          ['<PairPos Format="2">',
    786                           '  <Coverage>',
    787                           '    <Glyph value="A"/>',
    788                           '    <Glyph value="B"/>',
    789                           '    <Glyph value="C"/>',
    790                           '  </Coverage>',
    791                           '  <ValueFormat1 value="3"/>',
    792                           '  <ValueFormat2 value="1"/>',
    793                           '  <ClassDef1>',
    794                           '    <ClassDef glyph="A" class="1"/>',
    795                           '  </ClassDef1>',
    796                           '  <ClassDef2>',
    797                           '    <ClassDef glyph="one" class="1"/>',
    798                           '    <ClassDef glyph="two" class="1"/>',
    799                           '    <ClassDef glyph="zero" class="2"/>',
    800                           '  </ClassDef2>',
    801                           '  <!-- Class1Count=2 -->',
    802                           '  <!-- Class2Count=3 -->',
    803                           '  <Class1Record index="0">',
    804                           '    <Class2Record index="0">',
    805                           '    </Class2Record>',
    806                           '    <Class2Record index="1">',
    807                           '    </Class2Record>',
    808                           '    <Class2Record index="2">',
    809                           '      <Value1 XPlacement="-80" YPlacement="-20"/>',
    810                           '      <Value2 XPlacement="-50"/>',
    811                           '    </Class2Record>',
    812                           '  </Class1Record>',
    813                           '  <Class1Record index="1">',
    814                           '    <Class2Record index="0">',
    815                           '    </Class2Record>',
    816                           '    <Class2Record index="1">',
    817                           '      <Value2 XPlacement="-20"/>',
    818                           '    </Class2Record>',
    819                           '    <Class2Record index="2">',
    820                           '      <Value1/>',
    821                           '      <Value2 XPlacement="-50"/>',
    822                           '    </Class2Record>',
    823                           '  </Class1Record>',
    824                           '</PairPos>'])
    825 
    826     def test_buildPairPosGlyphs(self):
    827         d50 = builder.buildValue({"XPlacement": -50})
    828         d8020 = builder.buildValue({"XPlacement": -80, "YPlacement": -20})
    829         subtables = builder.buildPairPosGlyphs({
    830             ("A", "zero"): (None, d50),
    831             ("A", "one"):  (d8020, d50),
    832         }, self.GLYPHMAP)
    833         self.assertEqual(sum([getXML(t.toXML) for t in subtables], []),
    834                          ['<PairPos Format="1">',
    835                           '  <Coverage>',
    836                           '    <Glyph value="A"/>',
    837                           '  </Coverage>',
    838                           '  <ValueFormat1 value="0"/>',
    839                           '  <ValueFormat2 value="1"/>',
    840                           '  <!-- PairSetCount=1 -->',
    841                           '  <PairSet index="0">',
    842                           '    <!-- PairValueCount=1 -->',
    843                           '    <PairValueRecord index="0">',
    844                           '      <SecondGlyph value="zero"/>',
    845                           '      <Value2 XPlacement="-50"/>',
    846                           '    </PairValueRecord>',
    847                           '  </PairSet>',
    848                           '</PairPos>',
    849                           '<PairPos Format="1">',
    850                           '  <Coverage>',
    851                           '    <Glyph value="A"/>',
    852                           '  </Coverage>',
    853                           '  <ValueFormat1 value="3"/>',
    854                           '  <ValueFormat2 value="1"/>',
    855                           '  <!-- PairSetCount=1 -->',
    856                           '  <PairSet index="0">',
    857                           '    <!-- PairValueCount=1 -->',
    858                           '    <PairValueRecord index="0">',
    859                           '      <SecondGlyph value="one"/>',
    860                           '      <Value1 XPlacement="-80" YPlacement="-20"/>',
    861                           '      <Value2 XPlacement="-50"/>',
    862                           '    </PairValueRecord>',
    863                           '  </PairSet>',
    864                           '</PairPos>'])
    865 
    866     def test_buildPairPosGlyphsSubtable(self):
    867         d20 = builder.buildValue({"XPlacement": -20})
    868         d50 = builder.buildValue({"XPlacement": -50})
    869         d0 = builder.buildValue({})
    870         d8020 = builder.buildValue({"XPlacement": -80, "YPlacement": -20})
    871         subtable = builder.buildPairPosGlyphsSubtable({
    872             ("A", "zero"): (d0, d50),
    873             ("A", "one"):  (None, d20),
    874             ("B", "five"): (d8020, d50),
    875         }, self.GLYPHMAP)
    876         self.assertEqual(getXML(subtable.toXML),
    877                          ['<PairPos Format="1">',
    878                           '  <Coverage>',
    879                           '    <Glyph value="A"/>',
    880                           '    <Glyph value="B"/>',
    881                           '  </Coverage>',
    882                           '  <ValueFormat1 value="3"/>',
    883                           '  <ValueFormat2 value="1"/>',
    884                           '  <!-- PairSetCount=2 -->',
    885                           '  <PairSet index="0">',
    886                           '    <!-- PairValueCount=2 -->',
    887                           '    <PairValueRecord index="0">',
    888                           '      <SecondGlyph value="zero"/>',
    889                           '      <Value2 XPlacement="-50"/>',
    890                           '    </PairValueRecord>',
    891                           '    <PairValueRecord index="1">',
    892                           '      <SecondGlyph value="one"/>',
    893                           '      <Value2 XPlacement="-20"/>',
    894                           '    </PairValueRecord>',
    895                           '  </PairSet>',
    896                           '  <PairSet index="1">',
    897                           '    <!-- PairValueCount=1 -->',
    898                           '    <PairValueRecord index="0">',
    899                           '      <SecondGlyph value="five"/>',
    900                           '      <Value1 XPlacement="-80" YPlacement="-20"/>',
    901                           '      <Value2 XPlacement="-50"/>',
    902                           '    </PairValueRecord>',
    903                           '  </PairSet>',
    904                           '</PairPos>'])
    905 
    906     def test_buildSinglePos(self):
    907         subtables = builder.buildSinglePos({
    908             "one": builder.buildValue({"XPlacement": 500}),
    909             "two": builder.buildValue({"XPlacement": 500}),
    910             "three": builder.buildValue({"XPlacement": 200}),
    911             "four": builder.buildValue({"XPlacement": 400}),
    912             "five": builder.buildValue({"XPlacement": 500}),
    913             "six": builder.buildValue({"YPlacement": -6}),
    914         }, self.GLYPHMAP)
    915         self.assertEqual(sum([getXML(t.toXML) for t in subtables], []),
    916                          ['<SinglePos Format="1">',
    917                           '  <Coverage>',
    918                           '    <Glyph value="one"/>',
    919                           '    <Glyph value="two"/>',
    920                           '    <Glyph value="five"/>',
    921                           '  </Coverage>',
    922                           '  <ValueFormat value="1"/>',
    923                           '  <Value XPlacement="500"/>',
    924                           '</SinglePos>',
    925                           '<SinglePos Format="2">',
    926                           '  <Coverage>',
    927                           '    <Glyph value="three"/>',
    928                           '    <Glyph value="four"/>',
    929                           '  </Coverage>',
    930                           '  <ValueFormat value="1"/>',
    931                           '  <!-- ValueCount=2 -->',
    932                           '  <Value index="0" XPlacement="200"/>',
    933                           '  <Value index="1" XPlacement="400"/>',
    934                           '</SinglePos>',
    935                           '<SinglePos Format="1">',
    936                           '  <Coverage>',
    937                           '    <Glyph value="six"/>',
    938                           '  </Coverage>',
    939                           '  <ValueFormat value="2"/>',
    940                           '  <Value YPlacement="-6"/>',
    941                           '</SinglePos>'])
    942 
    943     def test_buildSinglePos_ValueFormat0(self):
    944         subtables = builder.buildSinglePos({
    945             "zero": builder.buildValue({})
    946         }, self.GLYPHMAP)
    947         self.assertEqual(sum([getXML(t.toXML) for t in subtables], []),
    948                          ['<SinglePos Format="1">',
    949                           '  <Coverage>',
    950                           '    <Glyph value="zero"/>',
    951                           '  </Coverage>',
    952                           '  <ValueFormat value="0"/>',
    953                           '</SinglePos>'])
    954 
    955     def test_buildSinglePosSubtable_format1(self):
    956         subtable = builder.buildSinglePosSubtable({
    957             "one": builder.buildValue({"XPlacement": 777}),
    958             "two": builder.buildValue({"XPlacement": 777}),
    959         }, self.GLYPHMAP)
    960         self.assertEqual(getXML(subtable.toXML),
    961                          ['<SinglePos Format="1">',
    962                           '  <Coverage>',
    963                           '    <Glyph value="one"/>',
    964                           '    <Glyph value="two"/>',
    965                           '  </Coverage>',
    966                           '  <ValueFormat value="1"/>',
    967                           '  <Value XPlacement="777"/>',
    968                           '</SinglePos>'])
    969 
    970     def test_buildSinglePosSubtable_format2(self):
    971         subtable = builder.buildSinglePosSubtable({
    972             "one": builder.buildValue({"XPlacement": 777}),
    973             "two": builder.buildValue({"YPlacement": -888}),
    974         }, self.GLYPHMAP)
    975         self.assertEqual(getXML(subtable.toXML),
    976                          ['<SinglePos Format="2">',
    977                           '  <Coverage>',
    978                           '    <Glyph value="one"/>',
    979                           '    <Glyph value="two"/>',
    980                           '  </Coverage>',
    981                           '  <ValueFormat value="3"/>',
    982                           '  <!-- ValueCount=2 -->',
    983                           '  <Value index="0" XPlacement="777"/>',
    984                           '  <Value index="1" YPlacement="-888"/>',
    985                           '</SinglePos>'])
    986 
    987     def test_buildValue(self):
    988         value = builder.buildValue({"XPlacement": 7, "YPlacement": 23})
    989         func = lambda writer, font: value.toXML(writer, font, valueName="Val")
    990         self.assertEqual(getXML(func),
    991                          ['<Val XPlacement="7" YPlacement="23"/>'])
    992 
    993     def test_getLigatureKey(self):
    994         components = lambda s: [tuple(word) for word in s.split()]
    995         c = components("fi fl ff ffi fff")
    996         c.sort(key=builder._getLigatureKey)
    997         self.assertEqual(c, components("fff ffi ff fi fl"))
    998 
    999     def test_getSinglePosValueKey(self):
   1000         device = builder.buildDevice({10:1, 11:3})
   1001         a1 = builder.buildValue({"XPlacement": 500, "XPlaDevice": device})
   1002         a2 = builder.buildValue({"XPlacement": 500, "XPlaDevice": device})
   1003         b = builder.buildValue({"XPlacement": 500})
   1004         keyA1 = builder._getSinglePosValueKey(a1)
   1005         keyA2 = builder._getSinglePosValueKey(a1)
   1006         keyB = builder._getSinglePosValueKey(b)
   1007         self.assertEqual(keyA1, keyA2)
   1008         self.assertEqual(hash(keyA1), hash(keyA2))
   1009         self.assertNotEqual(keyA1, keyB)
   1010         self.assertNotEqual(hash(keyA1), hash(keyB))
   1011 
   1012 
   1013 class ClassDefBuilderTest(unittest.TestCase):
   1014     def test_build_usingClass0(self):
   1015         b = builder.ClassDefBuilder(useClass0=True)
   1016         b.add({"aa", "bb"})
   1017         b.add({"a", "b"})
   1018         b.add({"c"})
   1019         b.add({"e", "f", "g", "h"})
   1020         cdef = b.build()
   1021         self.assertIsInstance(cdef, otTables.ClassDef)
   1022         self.assertEqual(cdef.classDefs, {
   1023             "a": 2,
   1024             "b": 2,
   1025             "c": 3,
   1026             "aa": 1,
   1027             "bb": 1
   1028         })
   1029 
   1030     def test_build_notUsingClass0(self):
   1031         b = builder.ClassDefBuilder(useClass0=False)
   1032         b.add({"a", "b"})
   1033         b.add({"c"})
   1034         b.add({"e", "f", "g", "h"})
   1035         cdef = b.build()
   1036         self.assertIsInstance(cdef, otTables.ClassDef)
   1037         self.assertEqual(cdef.classDefs, {
   1038             "a": 2,
   1039             "b": 2,
   1040             "c": 3,
   1041             "e": 1,
   1042             "f": 1,
   1043             "g": 1,
   1044             "h": 1
   1045         })
   1046 
   1047     def test_canAdd(self):
   1048         b = builder.ClassDefBuilder(useClass0=True)
   1049         b.add({"a", "b", "c", "d"})
   1050         b.add({"e", "f"})
   1051         self.assertTrue(b.canAdd({"a", "b", "c", "d"}))
   1052         self.assertTrue(b.canAdd({"e", "f"}))
   1053         self.assertTrue(b.canAdd({"g", "h", "i"}))
   1054         self.assertFalse(b.canAdd({"b", "c", "d"}))
   1055         self.assertFalse(b.canAdd({"a", "b", "c", "d", "e", "f"}))
   1056         self.assertFalse(b.canAdd({"d", "e", "f"}))
   1057         self.assertFalse(b.canAdd({"f"}))
   1058 
   1059 
   1060 if __name__ == "__main__":
   1061     import sys
   1062     sys.exit(unittest.main())
   1063