Example #1
0
 def test_buildMarkArray(self):
     markArray = builder.buildMarkArray(
         {
             "acute": (7, builder.buildAnchor(300, 800)),
             "grave": (2, builder.buildAnchor(10, 80)),
         },
         self.GLYPHMAP,
     )
     assert self.GLYPHMAP["grave"] < self.GLYPHMAP["acute"]
     assert getXML(markArray.toXML) == [
         "<MarkArray>",
         "  <!-- MarkCount=2 -->",
         '  <MarkRecord index="0">',
         '    <Class value="2"/>',
         '    <MarkAnchor Format="1">',
         '      <XCoordinate value="10"/>',
         '      <YCoordinate value="80"/>',
         "    </MarkAnchor>",
         "  </MarkRecord>",
         '  <MarkRecord index="1">',
         '    <Class value="7"/>',
         '    <MarkAnchor Format="1">',
         '      <XCoordinate value="300"/>',
         '      <YCoordinate value="800"/>',
         "    </MarkAnchor>",
         "  </MarkRecord>",
         "</MarkArray>",
     ]
Example #2
0
 def test_buildMarkArray(self):
     markArray = builder.buildMarkArray(
         {
             "acute": (7, builder.buildAnchor(300, 800)),
             "grave": (2, builder.buildAnchor(10, 80))
         }, self.GLYPHMAP)
     self.assertLess(self.GLYPHMAP["grave"], self.GLYPHMAP["acute"])
     self.assertEqual(
         getXML(markArray.toXML), '<MarkArray>'
         '  <!-- MarkCount=2 -->'
         '  <MarkRecord index="0">'
         '    <Class value="2"/>'
         '    <MarkAnchor Format="1">'
         '      <XCoordinate value="10"/>'
         '      <YCoordinate value="80"/>'
         '    </MarkAnchor>'
         '  </MarkRecord>'
         '  <MarkRecord index="1">'
         '    <Class value="7"/>'
         '    <MarkAnchor Format="1">'
         '      <XCoordinate value="300"/>'
         '      <YCoordinate value="800"/>'
         '    </MarkAnchor>'
         '  </MarkRecord>'
         '</MarkArray>')
Example #3
0
 def test_buildAnchor_format3(self):
     anchor = builder.buildAnchor(
         23,
         42,
         deviceX=builder.buildDevice({
             1: 1,
             0: 0
         }),
         deviceY=builder.buildDevice({7: 7}),
     )
     assert getXML(anchor.toXML) == [
         '<Anchor Format="3">',
         '  <XCoordinate value="23"/>',
         '  <YCoordinate value="42"/>',
         "  <XDeviceTable>",
         '    <StartSize value="0"/>',
         '    <EndSize value="1"/>',
         '    <DeltaFormat value="1"/>',
         '    <DeltaValue value="[0, 1]"/>',
         "  </XDeviceTable>",
         "  <YDeviceTable>",
         '    <StartSize value="7"/>',
         '    <EndSize value="7"/>',
         '    <DeltaFormat value="2"/>',
         '    <DeltaValue value="[7]"/>',
         "  </YDeviceTable>",
         "</Anchor>",
     ]
 def test_buildAnchor_format1(self):
     anchor = builder.buildAnchor(23, 42)
     self.assertEqual(getXML(anchor.toXML),
                      '<Anchor Format="1">'
                      '  <XCoordinate value="23"/>'
                      '  <YCoordinate value="42"/>'
                      '</Anchor>')
Example #5
0
def makeAnchor(anchor, ff):
    """Creates an OpenType anchor object from. If the anchor position contains
     a variable scalar, these variable scalars are saved in the ``GDEF`` variation
     store.

     Args:
        anchor: a tuple of x,y position. These elements may be numbers or
            :py:class:`VariableScalar` objects.

    Returns: an ``otTables.Anchor`` object.
    """
    x, y = anchor
    if isinstance(x, VariableScalar):
        x_def, x_index = x.add_to_variation_store(ff.varstorebuilder)
    else:
        x_def, x_index = int(x), None
    if isinstance(y, VariableScalar):
        y_def, y_index = y.add_to_variation_store(ff.varstorebuilder)
    else:
        y_def, y_index = int(y), None
    anchor = otl.buildAnchor(x_def, y_def)
    if x_index is not None and x_index != 0xFFFFFFFF:
        anchor.XDeviceTable = buildVarDevTable(x_index)
        anchor.Format = 3
    if y_index is not None and x_index != 0xFFFFFFFF:
        anchor.YDeviceTable = buildVarDevTable(y_index)
        anchor.Format = 3
    return anchor
Example #6
0
 def test_buildAnchor_format1(self):
     anchor = builder.buildAnchor(23, 42)
     self.assertEqual(getXML(anchor.toXML),
                      '<Anchor Format="1">'
                      '  <XCoordinate value="23"/>'
                      '  <YCoordinate value="42"/>'
                      '</Anchor>')
Example #7
0
 def test_buildAnchor_format1(self):
     anchor = builder.buildAnchor(23, 42)
     assert getXML(anchor.toXML) == [
         '<Anchor Format="1">',
         '  <XCoordinate value="23"/>',
         '  <YCoordinate value="42"/>',
         "</Anchor>",
     ]
Example #8
0
 def test_buildAnchor_format2(self):
     anchor = builder.buildAnchor(23, 42, point=17)
     assert getXML(anchor.toXML) == [
         '<Anchor Format="2">',
         '  <XCoordinate value="23"/>',
         '  <YCoordinate value="42"/>',
         '  <AnchorPoint value="17"/>',
         "</Anchor>",
     ]
 def test_buildMarkRecord(self):
     rec = builder.buildMarkRecord(17, builder.buildAnchor(500, -20))
     self.assertEqual(getXML(rec.toXML),
                      '<MarkRecord> '
                      ' <Class value="17"/>'
                      '  <MarkAnchor Format="1">'
                      '    <XCoordinate value="500"/>'
                      '    <YCoordinate value="-20"/>'
                      '  </MarkAnchor>'
                      '</MarkRecord>')
Example #10
0
def makeOpenTypeAnchor(anchor):
    """ast.Anchor --> otTables.Anchor"""
    if anchor is None:
        return None
    deviceX, deviceY = None, None
    if anchor.xDeviceTable is not None:
        deviceX = otl.buildDevice(dict(anchor.xDeviceTable))
    if anchor.yDeviceTable is not None:
        deviceY = otl.buildDevice(dict(anchor.yDeviceTable))
    return otl.buildAnchor(anchor.x, anchor.y, anchor.contourpoint, deviceX, deviceY)
Example #11
0
 def test_buildMarkRecord(self):
     rec = builder.buildMarkRecord(17, builder.buildAnchor(500, -20))
     assert getXML(rec.toXML) == [
         "<MarkRecord>",
         '  <Class value="17"/>',
         '  <MarkAnchor Format="1">',
         '    <XCoordinate value="500"/>',
         '    <YCoordinate value="-20"/>',
         "  </MarkAnchor>",
         "</MarkRecord>",
     ]
Example #12
0
def makeOpenTypeAnchor(anchor):
    """ast.Anchor --> otTables.Anchor"""
    if anchor is None:
        return None
    deviceX, deviceY = None, None
    if anchor.xDeviceTable is not None:
        deviceX = otl.buildDevice(dict(anchor.xDeviceTable))
    if anchor.yDeviceTable is not None:
        deviceY = otl.buildDevice(dict(anchor.yDeviceTable))
    return otl.buildAnchor(anchor.x, anchor.y, anchor.contourpoint,
                           deviceX, deviceY)
Example #13
0
 def test_buildMarkArray(self):
     markArray = builder.buildMarkArray({
         "acute": (7, builder.buildAnchor(300, 800)),
         "grave": (2, builder.buildAnchor(10, 80))
     }, self.GLYPHMAP)
     self.assertLess(self.GLYPHMAP["grave"], self.GLYPHMAP["acute"])
     self.assertEqual(getXML(markArray.toXML),
                      '<MarkArray>'
                      '  <!-- MarkCount=2 -->'
                      '  <MarkRecord index="0">'
                      '    <Class value="2"/>'
                      '    <MarkAnchor Format="1">'
                      '      <XCoordinate value="10"/>'
                      '      <YCoordinate value="80"/>'
                      '    </MarkAnchor>'
                      '  </MarkRecord>'
                      '  <MarkRecord index="1">'
                      '    <Class value="7"/>'
                      '    <MarkAnchor Format="1">'
                      '      <XCoordinate value="300"/>'
                      '      <YCoordinate value="800"/>'
                      '    </MarkAnchor>'
                      '  </MarkRecord>'
                      '</MarkArray>')
Example #14
0
def test_splitMarkBasePos():
    from fontTools.otlLib.builder import buildAnchor, buildMarkBasePosSubtable

    marks = {
        "acutecomb": (0, buildAnchor(0, 600)),
        "gravecomb": (0, buildAnchor(0, 590)),
        "cedillacomb": (1, buildAnchor(0, 0)),
    }
    bases = {
        "a": {
            0: buildAnchor(350, 500),
            1: None,
        },
        "c": {
            0: buildAnchor(300, 700),
            1: buildAnchor(300, 0),
        },
    }
    glyphOrder = ["a", "c", "acutecomb", "gravecomb", "cedillacomb"]
    glyphMap = {g: i for i, g in enumerate(glyphOrder)}

    oldSubTable = buildMarkBasePosSubtable(marks, bases, glyphMap)
    oldSubTable.MarkCoverage.Format = oldSubTable.BaseCoverage.Format = 1
    newSubTable = otTables.MarkBasePos()

    ok = otTables.splitMarkBasePos(oldSubTable,
                                   newSubTable,
                                   overflowRecord=None)

    assert ok
    assert oldSubTable.Format == newSubTable.Format
    assert oldSubTable.MarkCoverage.glyphs == ["acutecomb", "gravecomb"]
    assert newSubTable.MarkCoverage.glyphs == ["cedillacomb"]
    assert newSubTable.MarkCoverage.Format == 1
    assert oldSubTable.BaseCoverage.glyphs == newSubTable.BaseCoverage.glyphs
    assert newSubTable.BaseCoverage.Format == 1
    assert oldSubTable.ClassCount == newSubTable.ClassCount == 1
    assert oldSubTable.MarkArray.MarkCount == 2
    assert newSubTable.MarkArray.MarkCount == 1
    assert oldSubTable.BaseArray.BaseCount == newSubTable.BaseArray.BaseCount
    assert newSubTable.BaseArray.BaseRecord[0].BaseAnchor[0] is None
    assert newSubTable.BaseArray.BaseRecord[1].BaseAnchor[0] == buildAnchor(
        300, 0)
Example #15
0
def test_splitMarkBasePos():
	from fontTools.otlLib.builder import buildAnchor, buildMarkBasePosSubtable

	marks = {
		"acutecomb": (0, buildAnchor(0, 600)),
		"gravecomb": (0, buildAnchor(0, 590)),
		"cedillacomb": (1, buildAnchor(0, 0)),
	}
	bases = {
		"a": {
			0: buildAnchor(350, 500),
			1: None,
		},
		"c": {
			0: buildAnchor(300, 700),
			1: buildAnchor(300, 0),
		},
	}
	glyphOrder = ["a", "c", "acutecomb", "gravecomb", "cedillacomb"]
	glyphMap = {g: i for i, g in enumerate(glyphOrder)}

	oldSubTable = buildMarkBasePosSubtable(marks, bases, glyphMap)
	oldSubTable.MarkCoverage.Format = oldSubTable.BaseCoverage.Format = 1
	newSubTable = otTables.MarkBasePos()

	ok = otTables.splitMarkBasePos(oldSubTable, newSubTable, overflowRecord=None)

	assert ok
	assert oldSubTable.Format == newSubTable.Format
	assert oldSubTable.MarkCoverage.glyphs == [
		"acutecomb", "gravecomb"
	]
	assert newSubTable.MarkCoverage.glyphs == ["cedillacomb"]
	assert newSubTable.MarkCoverage.Format == 1
	assert oldSubTable.BaseCoverage.glyphs == newSubTable.BaseCoverage.glyphs
	assert newSubTable.BaseCoverage.Format == 1
	assert oldSubTable.ClassCount == newSubTable.ClassCount == 1
	assert oldSubTable.MarkArray.MarkCount == 2
	assert newSubTable.MarkArray.MarkCount == 1
	assert oldSubTable.BaseArray.BaseCount == newSubTable.BaseArray.BaseCount
	assert newSubTable.BaseArray.BaseRecord[0].BaseAnchor[0] is None
	assert newSubTable.BaseArray.BaseRecord[1].BaseAnchor[0] == buildAnchor(300, 0)
Example #16
0
 def test_buildAnchor_format3(self):
     anchor = builder.buildAnchor(
         23, 42,
         deviceX=builder.buildDevice([(1, 1), (0, 0)]),
         deviceY=builder.buildDevice([(7, 7)]))
     self.assertEqual(getXML(anchor.toXML),
                      '<Anchor Format="3">'
                      '  <XCoordinate value="23"/>'
                      '  <YCoordinate value="42"/>'
                      '  <XDeviceTable>'
                      '    <StartSize value="0"/>'
                      '    <EndSize value="1"/>'
                      '    <DeltaFormat value="1"/>'
                      '    <DeltaValue value="[0, 1]"/>'
                      '  </XDeviceTable>'
                      '  <YDeviceTable>'
                      '    <StartSize value="7"/>'
                      '    <EndSize value="7"/>'
                      '    <DeltaFormat value="2"/>'
                      '    <DeltaValue value="[7]"/>'
                      '  </YDeviceTable>'
                      '</Anchor>')
Example #17
0
def test_splitMarkBasePos():
    from fontTools.otlLib.builder import buildAnchor, buildMarkBasePosSubtable

    marks = {
        "acutecomb": (0, buildAnchor(0, 600)),
        "gravecomb": (0, buildAnchor(0, 590)),
        "cedillacomb": (1, buildAnchor(0, 0)),
    }
    bases = {
        "a": {
            0: buildAnchor(350, 500),
            1: None,
        },
        "c": {
            0: buildAnchor(300, 700),
            1: buildAnchor(300, 0),
        },
    }
    glyphOrder = ["a", "c", "acutecomb", "gravecomb", "cedillacomb"]
    glyphMap = {g: i for i, g in enumerate(glyphOrder)}

    oldSubTable = buildMarkBasePosSubtable(marks, bases, glyphMap)
    newSubTable = otTables.MarkBasePos()

    ok = otTables.splitMarkBasePos(oldSubTable,
                                   newSubTable,
                                   overflowRecord=None)

    assert ok

    assert getXML(oldSubTable.toXML) == [
        '<MarkBasePos Format="1">',
        '  <MarkCoverage>',
        '    <Glyph value="acutecomb"/>',
        '    <Glyph value="gravecomb"/>',
        '  </MarkCoverage>',
        '  <BaseCoverage>',
        '    <Glyph value="a"/>',
        '    <Glyph value="c"/>',
        '  </BaseCoverage>',
        '  <!-- ClassCount=1 -->',
        '  <MarkArray>',
        '    <!-- MarkCount=2 -->',
        '    <MarkRecord index="0">',
        '      <Class value="0"/>',
        '      <MarkAnchor Format="1">',
        '        <XCoordinate value="0"/>',
        '        <YCoordinate value="600"/>',
        '      </MarkAnchor>',
        '    </MarkRecord>',
        '    <MarkRecord index="1">',
        '      <Class value="0"/>',
        '      <MarkAnchor Format="1">',
        '        <XCoordinate value="0"/>',
        '        <YCoordinate value="590"/>',
        '      </MarkAnchor>',
        '    </MarkRecord>',
        '  </MarkArray>',
        '  <BaseArray>',
        '    <!-- BaseCount=2 -->',
        '    <BaseRecord index="0">',
        '      <BaseAnchor index="0" Format="1">',
        '        <XCoordinate value="350"/>',
        '        <YCoordinate value="500"/>',
        '      </BaseAnchor>',
        '    </BaseRecord>',
        '    <BaseRecord index="1">',
        '      <BaseAnchor index="0" Format="1">',
        '        <XCoordinate value="300"/>',
        '        <YCoordinate value="700"/>',
        '      </BaseAnchor>',
        '    </BaseRecord>',
        '  </BaseArray>',
        '</MarkBasePos>',
    ]

    assert getXML(newSubTable.toXML) == [
        '<MarkBasePos Format="1">',
        '  <MarkCoverage>',
        '    <Glyph value="cedillacomb"/>',
        '  </MarkCoverage>',
        '  <BaseCoverage>',
        '    <Glyph value="a"/>',
        '    <Glyph value="c"/>',
        '  </BaseCoverage>',
        '  <!-- ClassCount=1 -->',
        '  <MarkArray>',
        '    <!-- MarkCount=1 -->',
        '    <MarkRecord index="0">',
        '      <Class value="0"/>',
        '      <MarkAnchor Format="1">',
        '        <XCoordinate value="0"/>',
        '        <YCoordinate value="0"/>',
        '      </MarkAnchor>',
        '    </MarkRecord>',
        '  </MarkArray>',
        '  <BaseArray>',
        '    <!-- BaseCount=2 -->',
        '    <BaseRecord index="0">',
        '      <BaseAnchor index="0" empty="1"/>',
        '    </BaseRecord>',
        '    <BaseRecord index="1">',
        '      <BaseAnchor index="0" Format="1">',
        '        <XCoordinate value="300"/>',
        '        <YCoordinate value="0"/>',
        '      </BaseAnchor>',
        '    </BaseRecord>',
        '  </BaseArray>',
        '</MarkBasePos>',
    ]
Example #18
0
class BuilderTest(object):
    GLYPHS = (".notdef space zero one two three four five six "
              "A B C a b c grave acute cedilla f_f_i f_i c_t").split()
    GLYPHMAP = {name: num for num, name in enumerate(GLYPHS)}

    ANCHOR1 = builder.buildAnchor(11, -11)
    ANCHOR2 = builder.buildAnchor(22, -22)
    ANCHOR3 = builder.buildAnchor(33, -33)

    def test_buildAnchor_format1(self):
        anchor = builder.buildAnchor(23, 42)
        assert getXML(anchor.toXML) == [
            '<Anchor Format="1">',
            '  <XCoordinate value="23"/>',
            '  <YCoordinate value="42"/>',
            "</Anchor>",
        ]

    def test_buildAnchor_format2(self):
        anchor = builder.buildAnchor(23, 42, point=17)
        assert getXML(anchor.toXML) == [
            '<Anchor Format="2">',
            '  <XCoordinate value="23"/>',
            '  <YCoordinate value="42"/>',
            '  <AnchorPoint value="17"/>',
            "</Anchor>",
        ]

    def test_buildAnchor_format3(self):
        anchor = builder.buildAnchor(
            23,
            42,
            deviceX=builder.buildDevice({
                1: 1,
                0: 0
            }),
            deviceY=builder.buildDevice({7: 7}),
        )
        assert getXML(anchor.toXML) == [
            '<Anchor Format="3">',
            '  <XCoordinate value="23"/>',
            '  <YCoordinate value="42"/>',
            "  <XDeviceTable>",
            '    <StartSize value="0"/>',
            '    <EndSize value="1"/>',
            '    <DeltaFormat value="1"/>',
            '    <DeltaValue value="[0, 1]"/>',
            "  </XDeviceTable>",
            "  <YDeviceTable>",
            '    <StartSize value="7"/>',
            '    <EndSize value="7"/>',
            '    <DeltaFormat value="2"/>',
            '    <DeltaValue value="[7]"/>',
            "  </YDeviceTable>",
            "</Anchor>",
        ]

    def test_buildAttachList(self):
        attachList = builder.buildAttachList({
            "zero": [23, 7],
            "one": [1]
        }, self.GLYPHMAP)
        assert getXML(attachList.toXML) == [
            "<AttachList>",
            "  <Coverage>",
            '    <Glyph value="zero"/>',
            '    <Glyph value="one"/>',
            "  </Coverage>",
            "  <!-- GlyphCount=2 -->",
            '  <AttachPoint index="0">',
            "    <!-- PointCount=2 -->",
            '    <PointIndex index="0" value="7"/>',
            '    <PointIndex index="1" value="23"/>',
            "  </AttachPoint>",
            '  <AttachPoint index="1">',
            "    <!-- PointCount=1 -->",
            '    <PointIndex index="0" value="1"/>',
            "  </AttachPoint>",
            "</AttachList>",
        ]

    def test_buildAttachList_empty(self):
        assert builder.buildAttachList({}, self.GLYPHMAP) is None

    def test_buildAttachPoint(self):
        attachPoint = builder.buildAttachPoint([7, 3])
        assert getXML(attachPoint.toXML) == [
            "<AttachPoint>",
            "  <!-- PointCount=2 -->",
            '  <PointIndex index="0" value="3"/>',
            '  <PointIndex index="1" value="7"/>',
            "</AttachPoint>",
        ]

    def test_buildAttachPoint_empty(self):
        assert builder.buildAttachPoint([]) is None

    def test_buildAttachPoint_duplicate(self):
        attachPoint = builder.buildAttachPoint([7, 3, 7])
        assert getXML(attachPoint.toXML) == [
            "<AttachPoint>",
            "  <!-- PointCount=2 -->",
            '  <PointIndex index="0" value="3"/>',
            '  <PointIndex index="1" value="7"/>',
            "</AttachPoint>",
        ]

    def test_buildBaseArray(self):
        anchor = builder.buildAnchor
        baseArray = builder.buildBaseArray(
            {
                "a": {
                    2: anchor(300, 80)
                },
                "c": {
                    1: anchor(300, 80),
                    2: anchor(300, -20)
                }
            },
            numMarkClasses=4,
            glyphMap=self.GLYPHMAP,
        )
        assert getXML(baseArray.toXML) == [
            "<BaseArray>",
            "  <!-- BaseCount=2 -->",
            '  <BaseRecord index="0">',
            '    <BaseAnchor index="0" empty="1"/>',
            '    <BaseAnchor index="1" empty="1"/>',
            '    <BaseAnchor index="2" Format="1">',
            '      <XCoordinate value="300"/>',
            '      <YCoordinate value="80"/>',
            "    </BaseAnchor>",
            '    <BaseAnchor index="3" empty="1"/>',
            "  </BaseRecord>",
            '  <BaseRecord index="1">',
            '    <BaseAnchor index="0" empty="1"/>',
            '    <BaseAnchor index="1" Format="1">',
            '      <XCoordinate value="300"/>',
            '      <YCoordinate value="80"/>',
            "    </BaseAnchor>",
            '    <BaseAnchor index="2" Format="1">',
            '      <XCoordinate value="300"/>',
            '      <YCoordinate value="-20"/>',
            "    </BaseAnchor>",
            '    <BaseAnchor index="3" empty="1"/>',
            "  </BaseRecord>",
            "</BaseArray>",
        ]

    def test_buildBaseRecord(self):
        a = builder.buildAnchor
        rec = builder.buildBaseRecord([a(500, -20), None, a(300, -15)])
        assert getXML(rec.toXML) == [
            "<BaseRecord>",
            '  <BaseAnchor index="0" Format="1">',
            '    <XCoordinate value="500"/>',
            '    <YCoordinate value="-20"/>',
            "  </BaseAnchor>",
            '  <BaseAnchor index="1" empty="1"/>',
            '  <BaseAnchor index="2" Format="1">',
            '    <XCoordinate value="300"/>',
            '    <YCoordinate value="-15"/>',
            "  </BaseAnchor>",
            "</BaseRecord>",
        ]

    def test_buildCaretValueForCoord(self):
        caret = builder.buildCaretValueForCoord(500)
        assert getXML(caret.toXML) == [
            '<CaretValue Format="1">',
            '  <Coordinate value="500"/>',
            "</CaretValue>",
        ]

    def test_buildCaretValueForPoint(self):
        caret = builder.buildCaretValueForPoint(23)
        assert getXML(caret.toXML) == [
            '<CaretValue Format="2">',
            '  <CaretValuePoint value="23"/>',
            "</CaretValue>",
        ]

    def test_buildComponentRecord(self):
        a = builder.buildAnchor
        rec = builder.buildComponentRecord([a(500, -20), None, a(300, -15)])
        assert getXML(rec.toXML) == [
            "<ComponentRecord>",
            '  <LigatureAnchor index="0" Format="1">',
            '    <XCoordinate value="500"/>',
            '    <YCoordinate value="-20"/>',
            "  </LigatureAnchor>",
            '  <LigatureAnchor index="1" empty="1"/>',
            '  <LigatureAnchor index="2" Format="1">',
            '    <XCoordinate value="300"/>',
            '    <YCoordinate value="-15"/>',
            "  </LigatureAnchor>",
            "</ComponentRecord>",
        ]

    def test_buildComponentRecord_empty(self):
        assert builder.buildComponentRecord([]) is None

    def test_buildComponentRecord_None(self):
        assert builder.buildComponentRecord(None) is None

    def test_buildCoverage(self):
        cov = builder.buildCoverage({"two", "four"}, {"two": 2, "four": 4})
        assert getXML(cov.toXML) == [
            "<Coverage>",
            '  <Glyph value="two"/>',
            '  <Glyph value="four"/>',
            "</Coverage>",
        ]

    def test_buildCursivePos(self):
        pos = builder.buildCursivePosSubtable(
            {
                "two": (self.ANCHOR1, self.ANCHOR2),
                "four": (self.ANCHOR3, self.ANCHOR1)
            },
            self.GLYPHMAP,
        )
        assert getXML(pos.toXML) == [
            '<CursivePos Format="1">',
            "  <Coverage>",
            '    <Glyph value="two"/>',
            '    <Glyph value="four"/>',
            "  </Coverage>",
            "  <!-- EntryExitCount=2 -->",
            '  <EntryExitRecord index="0">',
            '    <EntryAnchor Format="1">',
            '      <XCoordinate value="11"/>',
            '      <YCoordinate value="-11"/>',
            "    </EntryAnchor>",
            '    <ExitAnchor Format="1">',
            '      <XCoordinate value="22"/>',
            '      <YCoordinate value="-22"/>',
            "    </ExitAnchor>",
            "  </EntryExitRecord>",
            '  <EntryExitRecord index="1">',
            '    <EntryAnchor Format="1">',
            '      <XCoordinate value="33"/>',
            '      <YCoordinate value="-33"/>',
            "    </EntryAnchor>",
            '    <ExitAnchor Format="1">',
            '      <XCoordinate value="11"/>',
            '      <YCoordinate value="-11"/>',
            "    </ExitAnchor>",
            "  </EntryExitRecord>",
            "</CursivePos>",
        ]

    def test_buildDevice_format1(self):
        device = builder.buildDevice({1: 1, 0: 0})
        assert getXML(device.toXML) == [
            "<Device>",
            '  <StartSize value="0"/>',
            '  <EndSize value="1"/>',
            '  <DeltaFormat value="1"/>',
            '  <DeltaValue value="[0, 1]"/>',
            "</Device>",
        ]

    def test_buildDevice_format2(self):
        device = builder.buildDevice({2: 2, 0: 1, 1: 0})
        assert getXML(device.toXML) == [
            "<Device>",
            '  <StartSize value="0"/>',
            '  <EndSize value="2"/>',
            '  <DeltaFormat value="2"/>',
            '  <DeltaValue value="[1, 0, 2]"/>',
            "</Device>",
        ]

    def test_buildDevice_format3(self):
        device = builder.buildDevice({5: 3, 1: 77})
        assert getXML(device.toXML) == [
            "<Device>",
            '  <StartSize value="1"/>',
            '  <EndSize value="5"/>',
            '  <DeltaFormat value="3"/>',
            '  <DeltaValue value="[77, 0, 0, 0, 3]"/>',
            "</Device>",
        ]

    def test_buildLigatureArray(self):
        anchor = builder.buildAnchor
        ligatureArray = builder.buildLigatureArray(
            {
                "f_i": [{
                    2: anchor(300, -20)
                }, {}],
                "c_t": [{}, {
                    1: anchor(500, 350),
                    2: anchor(1300, -20)
                }],
            },
            numMarkClasses=4,
            glyphMap=self.GLYPHMAP,
        )
        assert getXML(ligatureArray.toXML) == [
            "<LigatureArray>",
            "  <!-- LigatureCount=2 -->",
            '  <LigatureAttach index="0">',  # f_i
            "    <!-- ComponentCount=2 -->",
            '    <ComponentRecord index="0">',
            '      <LigatureAnchor index="0" empty="1"/>',
            '      <LigatureAnchor index="1" empty="1"/>',
            '      <LigatureAnchor index="2" Format="1">',
            '        <XCoordinate value="300"/>',
            '        <YCoordinate value="-20"/>',
            "      </LigatureAnchor>",
            '      <LigatureAnchor index="3" empty="1"/>',
            "    </ComponentRecord>",
            '    <ComponentRecord index="1">',
            '      <LigatureAnchor index="0" empty="1"/>',
            '      <LigatureAnchor index="1" empty="1"/>',
            '      <LigatureAnchor index="2" empty="1"/>',
            '      <LigatureAnchor index="3" empty="1"/>',
            "    </ComponentRecord>",
            "  </LigatureAttach>",
            '  <LigatureAttach index="1">',
            "    <!-- ComponentCount=2 -->",
            '    <ComponentRecord index="0">',
            '      <LigatureAnchor index="0" empty="1"/>',
            '      <LigatureAnchor index="1" empty="1"/>',
            '      <LigatureAnchor index="2" empty="1"/>',
            '      <LigatureAnchor index="3" empty="1"/>',
            "    </ComponentRecord>",
            '    <ComponentRecord index="1">',
            '      <LigatureAnchor index="0" empty="1"/>',
            '      <LigatureAnchor index="1" Format="1">',
            '        <XCoordinate value="500"/>',
            '        <YCoordinate value="350"/>',
            "      </LigatureAnchor>",
            '      <LigatureAnchor index="2" Format="1">',
            '        <XCoordinate value="1300"/>',
            '        <YCoordinate value="-20"/>',
            "      </LigatureAnchor>",
            '      <LigatureAnchor index="3" empty="1"/>',
            "    </ComponentRecord>",
            "  </LigatureAttach>",
            "</LigatureArray>",
        ]

    def test_buildLigatureAttach(self):
        anchor = builder.buildAnchor
        attach = builder.buildLigatureAttach([[anchor(500, -10), None],
                                              [None,
                                               anchor(300, -20), None]])
        assert getXML(attach.toXML) == [
            "<LigatureAttach>",
            "  <!-- ComponentCount=2 -->",
            '  <ComponentRecord index="0">',
            '    <LigatureAnchor index="0" Format="1">',
            '      <XCoordinate value="500"/>',
            '      <YCoordinate value="-10"/>',
            "    </LigatureAnchor>",
            '    <LigatureAnchor index="1" empty="1"/>',
            "  </ComponentRecord>",
            '  <ComponentRecord index="1">',
            '    <LigatureAnchor index="0" empty="1"/>',
            '    <LigatureAnchor index="1" Format="1">',
            '      <XCoordinate value="300"/>',
            '      <YCoordinate value="-20"/>',
            "    </LigatureAnchor>",
            '    <LigatureAnchor index="2" empty="1"/>',
            "  </ComponentRecord>",
            "</LigatureAttach>",
        ]

    def test_buildLigatureAttach_emptyComponents(self):
        attach = builder.buildLigatureAttach([[], None])
        assert getXML(attach.toXML) == [
            "<LigatureAttach>",
            "  <!-- ComponentCount=2 -->",
            '  <ComponentRecord index="0" empty="1"/>',
            '  <ComponentRecord index="1" empty="1"/>',
            "</LigatureAttach>",
        ]

    def test_buildLigatureAttach_noComponents(self):
        attach = builder.buildLigatureAttach([])
        assert getXML(attach.toXML) == [
            "<LigatureAttach>",
            "  <!-- ComponentCount=0 -->",
            "</LigatureAttach>",
        ]

    def test_buildLigCaretList(self):
        carets = builder.buildLigCaretList({"f_f_i": [300, 600]},
                                           {"c_t": [42]}, self.GLYPHMAP)
        assert getXML(carets.toXML) == [
            "<LigCaretList>",
            "  <Coverage>",
            '    <Glyph value="f_f_i"/>',
            '    <Glyph value="c_t"/>',
            "  </Coverage>",
            "  <!-- LigGlyphCount=2 -->",
            '  <LigGlyph index="0">',
            "    <!-- CaretCount=2 -->",
            '    <CaretValue index="0" Format="1">',
            '      <Coordinate value="300"/>',
            "    </CaretValue>",
            '    <CaretValue index="1" Format="1">',
            '      <Coordinate value="600"/>',
            "    </CaretValue>",
            "  </LigGlyph>",
            '  <LigGlyph index="1">',
            "    <!-- CaretCount=1 -->",
            '    <CaretValue index="0" Format="2">',
            '      <CaretValuePoint value="42"/>',
            "    </CaretValue>",
            "  </LigGlyph>",
            "</LigCaretList>",
        ]

    def test_buildLigCaretList_bothCoordsAndPointsForSameGlyph(self):
        carets = builder.buildLigCaretList({"f_f_i": [300]}, {"f_f_i": [7]},
                                           self.GLYPHMAP)
        assert getXML(carets.toXML) == [
            "<LigCaretList>",
            "  <Coverage>",
            '    <Glyph value="f_f_i"/>',
            "  </Coverage>",
            "  <!-- LigGlyphCount=1 -->",
            '  <LigGlyph index="0">',
            "    <!-- CaretCount=2 -->",
            '    <CaretValue index="0" Format="1">',
            '      <Coordinate value="300"/>',
            "    </CaretValue>",
            '    <CaretValue index="1" Format="2">',
            '      <CaretValuePoint value="7"/>',
            "    </CaretValue>",
            "  </LigGlyph>",
            "</LigCaretList>",
        ]

    def test_buildLigCaretList_empty(self):
        assert builder.buildLigCaretList({}, {}, self.GLYPHMAP) is None

    def test_buildLigCaretList_None(self):
        assert builder.buildLigCaretList(None, None, self.GLYPHMAP) is None

    def test_buildLigGlyph_coords(self):
        lig = builder.buildLigGlyph([500, 800], None)
        assert getXML(lig.toXML) == [
            "<LigGlyph>",
            "  <!-- CaretCount=2 -->",
            '  <CaretValue index="0" Format="1">',
            '    <Coordinate value="500"/>',
            "  </CaretValue>",
            '  <CaretValue index="1" Format="1">',
            '    <Coordinate value="800"/>',
            "  </CaretValue>",
            "</LigGlyph>",
        ]

    def test_buildLigGlyph_empty(self):
        assert builder.buildLigGlyph([], []) is None

    def test_buildLigGlyph_None(self):
        assert builder.buildLigGlyph(None, None) is None

    def test_buildLigGlyph_points(self):
        lig = builder.buildLigGlyph(None, [2])
        assert getXML(lig.toXML) == [
            "<LigGlyph>",
            "  <!-- CaretCount=1 -->",
            '  <CaretValue index="0" Format="2">',
            '    <CaretValuePoint value="2"/>',
            "  </CaretValue>",
            "</LigGlyph>",
        ]

    def test_buildLookup(self):
        s1 = builder.buildSingleSubstSubtable({"one": "two"})
        s2 = builder.buildSingleSubstSubtable({"three": "four"})
        lookup = builder.buildLookup([s1, s2], flags=7)
        assert getXML(lookup.toXML) == [
            "<Lookup>",
            '  <LookupType value="1"/>',
            '  <LookupFlag value="7"/>',
            "  <!-- SubTableCount=2 -->",
            '  <SingleSubst index="0">',
            '    <Substitution in="one" out="two"/>',
            "  </SingleSubst>",
            '  <SingleSubst index="1">',
            '    <Substitution in="three" out="four"/>',
            "  </SingleSubst>",
            "</Lookup>",
        ]

    def test_buildLookup_badFlags(self):
        s = builder.buildSingleSubstSubtable({"one": "two"})
        with pytest.raises(
                AssertionError,
                match=("if markFilterSet is None, flags must not set "
                       "LOOKUP_FLAG_USE_MARK_FILTERING_SET; flags=0x0010"),
        ) as excinfo:
            builder.buildLookup([s],
                                builder.LOOKUP_FLAG_USE_MARK_FILTERING_SET,
                                None)
        with pytest.raises(
                AssertionError,
                match=("if markFilterSet is not None, flags must set "
                       "LOOKUP_FLAG_USE_MARK_FILTERING_SET; flags=0x0004"),
        ) as excinfo:
            builder.buildLookup([s], builder.LOOKUP_FLAG_IGNORE_LIGATURES, 777)

    def test_buildLookup_conflictingSubtableTypes(self):
        s1 = builder.buildSingleSubstSubtable({"one": "two"})
        s2 = builder.buildAlternateSubstSubtable({"one": ["two", "three"]})
        with pytest.raises(AssertionError,
                           match="all subtables must have the same LookupType"
                           ) as excinfo:
            builder.buildLookup([s1, s2])

    def test_buildLookup_noSubtables(self):
        assert builder.buildLookup([]) is None
        assert builder.buildLookup(None) is None
        assert builder.buildLookup([None]) is None
        assert builder.buildLookup([None, None]) is None

    def test_buildLookup_markFilterSet(self):
        s = builder.buildSingleSubstSubtable({"one": "two"})
        flags = (builder.LOOKUP_FLAG_RIGHT_TO_LEFT
                 | builder.LOOKUP_FLAG_USE_MARK_FILTERING_SET)
        lookup = builder.buildLookup([s], flags, markFilterSet=999)
        assert getXML(lookup.toXML) == [
            "<Lookup>",
            '  <LookupType value="1"/>',
            '  <LookupFlag value="17"/>',
            "  <!-- SubTableCount=1 -->",
            '  <SingleSubst index="0">',
            '    <Substitution in="one" out="two"/>',
            "  </SingleSubst>",
            '  <MarkFilteringSet value="999"/>',
            "</Lookup>",
        ]

    def test_buildMarkArray(self):
        markArray = builder.buildMarkArray(
            {
                "acute": (7, builder.buildAnchor(300, 800)),
                "grave": (2, builder.buildAnchor(10, 80)),
            },
            self.GLYPHMAP,
        )
        assert self.GLYPHMAP["grave"] < self.GLYPHMAP["acute"]
        assert getXML(markArray.toXML) == [
            "<MarkArray>",
            "  <!-- MarkCount=2 -->",
            '  <MarkRecord index="0">',
            '    <Class value="2"/>',
            '    <MarkAnchor Format="1">',
            '      <XCoordinate value="10"/>',
            '      <YCoordinate value="80"/>',
            "    </MarkAnchor>",
            "  </MarkRecord>",
            '  <MarkRecord index="1">',
            '    <Class value="7"/>',
            '    <MarkAnchor Format="1">',
            '      <XCoordinate value="300"/>',
            '      <YCoordinate value="800"/>',
            "    </MarkAnchor>",
            "  </MarkRecord>",
            "</MarkArray>",
        ]

    def test_buildMarkBasePosSubtable(self):
        anchor = builder.buildAnchor
        marks = {
            "acute": (0, anchor(300, 700)),
            "cedilla": (1, anchor(300, -100)),
            "grave": (0, anchor(300, 700)),
        }
        bases = {
            # Make sure we can handle missing entries.
            "A": {},  # no entry for any markClass
            "B": {
                0: anchor(500, 900)
            },  # only markClass 0 specified
            "C": {
                1: anchor(500, -10)
            },  # only markClass 1 specified
            "a": {
                0: anchor(500, 400),
                1: anchor(500, -20)
            },
            "b": {
                0: anchor(500, 800),
                1: anchor(500, -20)
            },
        }
        table = builder.buildMarkBasePosSubtable(marks, bases, self.GLYPHMAP)
        assert getXML(table.toXML) == [
            '<MarkBasePos Format="1">',
            "  <MarkCoverage>",
            '    <Glyph value="grave"/>',
            '    <Glyph value="acute"/>',
            '    <Glyph value="cedilla"/>',
            "  </MarkCoverage>",
            "  <BaseCoverage>",
            '    <Glyph value="A"/>',
            '    <Glyph value="B"/>',
            '    <Glyph value="C"/>',
            '    <Glyph value="a"/>',
            '    <Glyph value="b"/>',
            "  </BaseCoverage>",
            "  <!-- ClassCount=2 -->",
            "  <MarkArray>",
            "    <!-- MarkCount=3 -->",
            '    <MarkRecord index="0">',  # grave
            '      <Class value="0"/>',
            '      <MarkAnchor Format="1">',
            '        <XCoordinate value="300"/>',
            '        <YCoordinate value="700"/>',
            "      </MarkAnchor>",
            "    </MarkRecord>",
            '    <MarkRecord index="1">',  # acute
            '      <Class value="0"/>',
            '      <MarkAnchor Format="1">',
            '        <XCoordinate value="300"/>',
            '        <YCoordinate value="700"/>',
            "      </MarkAnchor>",
            "    </MarkRecord>",
            '    <MarkRecord index="2">',  # cedilla
            '      <Class value="1"/>',
            '      <MarkAnchor Format="1">',
            '        <XCoordinate value="300"/>',
            '        <YCoordinate value="-100"/>',
            "      </MarkAnchor>",
            "    </MarkRecord>",
            "  </MarkArray>",
            "  <BaseArray>",
            "    <!-- BaseCount=5 -->",
            '    <BaseRecord index="0">',  # A
            '      <BaseAnchor index="0" empty="1"/>',
            '      <BaseAnchor index="1" empty="1"/>',
            "    </BaseRecord>",
            '    <BaseRecord index="1">',  # B
            '      <BaseAnchor index="0" Format="1">',
            '        <XCoordinate value="500"/>',
            '        <YCoordinate value="900"/>',
            "      </BaseAnchor>",
            '      <BaseAnchor index="1" empty="1"/>',
            "    </BaseRecord>",
            '    <BaseRecord index="2">',  # C
            '      <BaseAnchor index="0" empty="1"/>',
            '      <BaseAnchor index="1" Format="1">',
            '        <XCoordinate value="500"/>',
            '        <YCoordinate value="-10"/>',
            "      </BaseAnchor>",
            "    </BaseRecord>",
            '    <BaseRecord index="3">',  # a
            '      <BaseAnchor index="0" Format="1">',
            '        <XCoordinate value="500"/>',
            '        <YCoordinate value="400"/>',
            "      </BaseAnchor>",
            '      <BaseAnchor index="1" Format="1">',
            '        <XCoordinate value="500"/>',
            '        <YCoordinate value="-20"/>',
            "      </BaseAnchor>",
            "    </BaseRecord>",
            '    <BaseRecord index="4">',  # b
            '      <BaseAnchor index="0" Format="1">',
            '        <XCoordinate value="500"/>',
            '        <YCoordinate value="800"/>',
            "      </BaseAnchor>",
            '      <BaseAnchor index="1" Format="1">',
            '        <XCoordinate value="500"/>',
            '        <YCoordinate value="-20"/>',
            "      </BaseAnchor>",
            "    </BaseRecord>",
            "  </BaseArray>",
            "</MarkBasePos>",
        ]

    def test_buildMarkGlyphSetsDef(self):
        marksets = builder.buildMarkGlyphSetsDef(
            [{"acute", "grave"}, {"cedilla", "grave"}], self.GLYPHMAP)
        assert getXML(marksets.toXML) == [
            "<MarkGlyphSetsDef>",
            '  <MarkSetTableFormat value="1"/>',
            "  <!-- MarkSetCount=2 -->",
            '  <Coverage index="0">',
            '    <Glyph value="grave"/>',
            '    <Glyph value="acute"/>',
            "  </Coverage>",
            '  <Coverage index="1">',
            '    <Glyph value="grave"/>',
            '    <Glyph value="cedilla"/>',
            "  </Coverage>",
            "</MarkGlyphSetsDef>",
        ]

    def test_buildMarkGlyphSetsDef_empty(self):
        assert builder.buildMarkGlyphSetsDef([], self.GLYPHMAP) is None

    def test_buildMarkGlyphSetsDef_None(self):
        assert builder.buildMarkGlyphSetsDef(None, self.GLYPHMAP) is None

    def test_buildMarkLigPosSubtable(self):
        anchor = builder.buildAnchor
        marks = {
            "acute": (0, anchor(300, 700)),
            "cedilla": (1, anchor(300, -100)),
            "grave": (0, anchor(300, 700)),
        }
        bases = {
            "f_i": [{}, {
                0: anchor(200, 400)
            }],  # nothing on f; only 1 on i
            "c_t": [
                {
                    0: anchor(500, 600),
                    1: anchor(500, -20)
                },  # c
                {
                    0: anchor(1300, 800),
                    1: anchor(1300, -20)
                },  # t
            ],
        }
        table = builder.buildMarkLigPosSubtable(marks, bases, self.GLYPHMAP)
        assert getXML(table.toXML) == [
            '<MarkLigPos Format="1">',
            "  <MarkCoverage>",
            '    <Glyph value="grave"/>',
            '    <Glyph value="acute"/>',
            '    <Glyph value="cedilla"/>',
            "  </MarkCoverage>",
            "  <LigatureCoverage>",
            '    <Glyph value="f_i"/>',
            '    <Glyph value="c_t"/>',
            "  </LigatureCoverage>",
            "  <!-- ClassCount=2 -->",
            "  <MarkArray>",
            "    <!-- MarkCount=3 -->",
            '    <MarkRecord index="0">',
            '      <Class value="0"/>',
            '      <MarkAnchor Format="1">',
            '        <XCoordinate value="300"/>',
            '        <YCoordinate value="700"/>',
            "      </MarkAnchor>",
            "    </MarkRecord>",
            '    <MarkRecord index="1">',
            '      <Class value="0"/>',
            '      <MarkAnchor Format="1">',
            '        <XCoordinate value="300"/>',
            '        <YCoordinate value="700"/>',
            "      </MarkAnchor>",
            "    </MarkRecord>",
            '    <MarkRecord index="2">',
            '      <Class value="1"/>',
            '      <MarkAnchor Format="1">',
            '        <XCoordinate value="300"/>',
            '        <YCoordinate value="-100"/>',
            "      </MarkAnchor>",
            "    </MarkRecord>",
            "  </MarkArray>",
            "  <LigatureArray>",
            "    <!-- LigatureCount=2 -->",
            '    <LigatureAttach index="0">',
            "      <!-- ComponentCount=2 -->",
            '      <ComponentRecord index="0">',
            '        <LigatureAnchor index="0" empty="1"/>',
            '        <LigatureAnchor index="1" empty="1"/>',
            "      </ComponentRecord>",
            '      <ComponentRecord index="1">',
            '        <LigatureAnchor index="0" Format="1">',
            '          <XCoordinate value="200"/>',
            '          <YCoordinate value="400"/>',
            "        </LigatureAnchor>",
            '        <LigatureAnchor index="1" empty="1"/>',
            "      </ComponentRecord>",
            "    </LigatureAttach>",
            '    <LigatureAttach index="1">',
            "      <!-- ComponentCount=2 -->",
            '      <ComponentRecord index="0">',
            '        <LigatureAnchor index="0" Format="1">',
            '          <XCoordinate value="500"/>',
            '          <YCoordinate value="600"/>',
            "        </LigatureAnchor>",
            '        <LigatureAnchor index="1" Format="1">',
            '          <XCoordinate value="500"/>',
            '          <YCoordinate value="-20"/>',
            "        </LigatureAnchor>",
            "      </ComponentRecord>",
            '      <ComponentRecord index="1">',
            '        <LigatureAnchor index="0" Format="1">',
            '          <XCoordinate value="1300"/>',
            '          <YCoordinate value="800"/>',
            "        </LigatureAnchor>",
            '        <LigatureAnchor index="1" Format="1">',
            '          <XCoordinate value="1300"/>',
            '          <YCoordinate value="-20"/>',
            "        </LigatureAnchor>",
            "      </ComponentRecord>",
            "    </LigatureAttach>",
            "  </LigatureArray>",
            "</MarkLigPos>",
        ]

    def test_buildMarkRecord(self):
        rec = builder.buildMarkRecord(17, builder.buildAnchor(500, -20))
        assert getXML(rec.toXML) == [
            "<MarkRecord>",
            '  <Class value="17"/>',
            '  <MarkAnchor Format="1">',
            '    <XCoordinate value="500"/>',
            '    <YCoordinate value="-20"/>',
            "  </MarkAnchor>",
            "</MarkRecord>",
        ]

    def test_buildMark2Record(self):
        a = builder.buildAnchor
        rec = builder.buildMark2Record([a(500, -20), None, a(300, -15)])
        assert getXML(rec.toXML) == [
            "<Mark2Record>",
            '  <Mark2Anchor index="0" Format="1">',
            '    <XCoordinate value="500"/>',
            '    <YCoordinate value="-20"/>',
            "  </Mark2Anchor>",
            '  <Mark2Anchor index="1" empty="1"/>',
            '  <Mark2Anchor index="2" Format="1">',
            '    <XCoordinate value="300"/>',
            '    <YCoordinate value="-15"/>',
            "  </Mark2Anchor>",
            "</Mark2Record>",
        ]

    def test_buildPairPosClassesSubtable(self):
        d20 = builder.buildValue({"XPlacement": -20})
        d50 = builder.buildValue({"XPlacement": -50})
        d0 = builder.buildValue({})
        d8020 = builder.buildValue({"XPlacement": -80, "YPlacement": -20})
        subtable = builder.buildPairPosClassesSubtable(
            {
                (tuple("A"), tuple(["zero"])): (d0, d50),
                (tuple("A"), tuple(["one", "two"])): (None, d20),
                (tuple(["B", "C"]), tuple(["zero"])): (d8020, d50),
            },
            self.GLYPHMAP,
        )
        assert getXML(subtable.toXML) == [
            '<PairPos Format="2">',
            "  <Coverage>",
            '    <Glyph value="A"/>',
            '    <Glyph value="B"/>',
            '    <Glyph value="C"/>',
            "  </Coverage>",
            '  <ValueFormat1 value="3"/>',
            '  <ValueFormat2 value="1"/>',
            "  <ClassDef1>",
            '    <ClassDef glyph="A" class="1"/>',
            "  </ClassDef1>",
            "  <ClassDef2>",
            '    <ClassDef glyph="one" class="1"/>',
            '    <ClassDef glyph="two" class="1"/>',
            '    <ClassDef glyph="zero" class="2"/>',
            "  </ClassDef2>",
            "  <!-- Class1Count=2 -->",
            "  <!-- Class2Count=3 -->",
            '  <Class1Record index="0">',
            '    <Class2Record index="0">',
            "    </Class2Record>",
            '    <Class2Record index="1">',
            "    </Class2Record>",
            '    <Class2Record index="2">',
            '      <Value1 XPlacement="-80" YPlacement="-20"/>',
            '      <Value2 XPlacement="-50"/>',
            "    </Class2Record>",
            "  </Class1Record>",
            '  <Class1Record index="1">',
            '    <Class2Record index="0">',
            "    </Class2Record>",
            '    <Class2Record index="1">',
            '      <Value2 XPlacement="-20"/>',
            "    </Class2Record>",
            '    <Class2Record index="2">',
            "      <Value1/>",
            '      <Value2 XPlacement="-50"/>',
            "    </Class2Record>",
            "  </Class1Record>",
            "</PairPos>",
        ]

    def test_buildPairPosGlyphs(self):
        d50 = builder.buildValue({"XPlacement": -50})
        d8020 = builder.buildValue({"XPlacement": -80, "YPlacement": -20})
        subtables = builder.buildPairPosGlyphs(
            {
                ("A", "zero"): (None, d50),
                ("A", "one"): (d8020, d50)
            }, self.GLYPHMAP)
        assert sum([getXML(t.toXML) for t in subtables], []) == [
            '<PairPos Format="1">',
            "  <Coverage>",
            '    <Glyph value="A"/>',
            "  </Coverage>",
            '  <ValueFormat1 value="0"/>',
            '  <ValueFormat2 value="1"/>',
            "  <!-- PairSetCount=1 -->",
            '  <PairSet index="0">',
            "    <!-- PairValueCount=1 -->",
            '    <PairValueRecord index="0">',
            '      <SecondGlyph value="zero"/>',
            '      <Value2 XPlacement="-50"/>',
            "    </PairValueRecord>",
            "  </PairSet>",
            "</PairPos>",
            '<PairPos Format="1">',
            "  <Coverage>",
            '    <Glyph value="A"/>',
            "  </Coverage>",
            '  <ValueFormat1 value="3"/>',
            '  <ValueFormat2 value="1"/>',
            "  <!-- PairSetCount=1 -->",
            '  <PairSet index="0">',
            "    <!-- PairValueCount=1 -->",
            '    <PairValueRecord index="0">',
            '      <SecondGlyph value="one"/>',
            '      <Value1 XPlacement="-80" YPlacement="-20"/>',
            '      <Value2 XPlacement="-50"/>',
            "    </PairValueRecord>",
            "  </PairSet>",
            "</PairPos>",
        ]

    def test_buildPairPosGlyphsSubtable(self):
        d20 = builder.buildValue({"XPlacement": -20})
        d50 = builder.buildValue({"XPlacement": -50})
        d0 = builder.buildValue({})
        d8020 = builder.buildValue({"XPlacement": -80, "YPlacement": -20})
        subtable = builder.buildPairPosGlyphsSubtable(
            {
                ("A", "zero"): (d0, d50),
                ("A", "one"): (None, d20),
                ("B", "five"): (d8020, d50),
            },
            self.GLYPHMAP,
        )
        assert getXML(subtable.toXML) == [
            '<PairPos Format="1">',
            "  <Coverage>",
            '    <Glyph value="A"/>',
            '    <Glyph value="B"/>',
            "  </Coverage>",
            '  <ValueFormat1 value="3"/>',
            '  <ValueFormat2 value="1"/>',
            "  <!-- PairSetCount=2 -->",
            '  <PairSet index="0">',
            "    <!-- PairValueCount=2 -->",
            '    <PairValueRecord index="0">',
            '      <SecondGlyph value="zero"/>',
            '      <Value2 XPlacement="-50"/>',
            "    </PairValueRecord>",
            '    <PairValueRecord index="1">',
            '      <SecondGlyph value="one"/>',
            '      <Value2 XPlacement="-20"/>',
            "    </PairValueRecord>",
            "  </PairSet>",
            '  <PairSet index="1">',
            "    <!-- PairValueCount=1 -->",
            '    <PairValueRecord index="0">',
            '      <SecondGlyph value="five"/>',
            '      <Value1 XPlacement="-80" YPlacement="-20"/>',
            '      <Value2 XPlacement="-50"/>',
            "    </PairValueRecord>",
            "  </PairSet>",
            "</PairPos>",
        ]

    def test_buildSinglePos(self):
        subtables = builder.buildSinglePos(
            {
                "one": builder.buildValue({"XPlacement": 500}),
                "two": builder.buildValue({"XPlacement": 500}),
                "three": builder.buildValue({"XPlacement": 200}),
                "four": builder.buildValue({"XPlacement": 400}),
                "five": builder.buildValue({"XPlacement": 500}),
                "six": builder.buildValue({"YPlacement": -6}),
            },
            self.GLYPHMAP,
        )
        assert sum([getXML(t.toXML) for t in subtables], []) == [
            '<SinglePos Format="2">',
            "  <Coverage>",
            '    <Glyph value="one"/>',
            '    <Glyph value="two"/>',
            '    <Glyph value="three"/>',
            '    <Glyph value="four"/>',
            '    <Glyph value="five"/>',
            "  </Coverage>",
            '  <ValueFormat value="1"/>',
            "  <!-- ValueCount=5 -->",
            '  <Value index="0" XPlacement="500"/>',
            '  <Value index="1" XPlacement="500"/>',
            '  <Value index="2" XPlacement="200"/>',
            '  <Value index="3" XPlacement="400"/>',
            '  <Value index="4" XPlacement="500"/>',
            "</SinglePos>",
            '<SinglePos Format="1">',
            "  <Coverage>",
            '    <Glyph value="six"/>',
            "  </Coverage>",
            '  <ValueFormat value="2"/>',
            '  <Value YPlacement="-6"/>',
            "</SinglePos>",
        ]

    def test_buildSinglePos_ValueFormat0(self):
        subtables = builder.buildSinglePos({"zero": builder.buildValue({})},
                                           self.GLYPHMAP)
        assert sum([getXML(t.toXML) for t in subtables], []) == [
            '<SinglePos Format="1">',
            "  <Coverage>",
            '    <Glyph value="zero"/>',
            "  </Coverage>",
            '  <ValueFormat value="0"/>',
            "</SinglePos>",
        ]

    def test_buildSinglePosSubtable_format1(self):
        subtable = builder.buildSinglePosSubtable(
            {
                "one": builder.buildValue({"XPlacement": 777}),
                "two": builder.buildValue({"XPlacement": 777}),
            },
            self.GLYPHMAP,
        )
        assert getXML(subtable.toXML) == [
            '<SinglePos Format="1">',
            "  <Coverage>",
            '    <Glyph value="one"/>',
            '    <Glyph value="two"/>',
            "  </Coverage>",
            '  <ValueFormat value="1"/>',
            '  <Value XPlacement="777"/>',
            "</SinglePos>",
        ]

    def test_buildSinglePosSubtable_format2(self):
        subtable = builder.buildSinglePosSubtable(
            {
                "one": builder.buildValue({"XPlacement": 777}),
                "two": builder.buildValue({"YPlacement": -888}),
            },
            self.GLYPHMAP,
        )
        assert getXML(subtable.toXML) == [
            '<SinglePos Format="2">',
            "  <Coverage>",
            '    <Glyph value="one"/>',
            '    <Glyph value="two"/>',
            "  </Coverage>",
            '  <ValueFormat value="3"/>',
            "  <!-- ValueCount=2 -->",
            '  <Value index="0" XPlacement="777"/>',
            '  <Value index="1" YPlacement="-888"/>',
            "</SinglePos>",
        ]

    def test_buildValue(self):
        value = builder.buildValue({"XPlacement": 7, "YPlacement": 23})
        func = lambda writer, font: value.toXML(writer, font, valueName="Val")
        assert getXML(func) == ['<Val XPlacement="7" YPlacement="23"/>']

    def test_getLigatureKey(self):
        components = lambda s: [tuple(word) for word in s.split()]
        c = components("fi fl ff ffi fff")
        c.sort(key=builder._getLigatureKey)
        assert c == components("fff ffi ff fi fl")

    def test_getSinglePosValueKey(self):
        device = builder.buildDevice({10: 1, 11: 3})
        a1 = builder.buildValue({"XPlacement": 500, "XPlaDevice": device})
        a2 = builder.buildValue({"XPlacement": 500, "XPlaDevice": device})
        b = builder.buildValue({"XPlacement": 500})
        keyA1 = builder._getSinglePosValueKey(a1)
        keyA2 = builder._getSinglePosValueKey(a1)
        keyB = builder._getSinglePosValueKey(b)
        assert keyA1 == keyA2
        assert hash(keyA1) == hash(keyA2)
        assert keyA1 != keyB
        assert hash(keyA1) != hash(keyB)