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_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>')
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>')
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
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_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>')
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)
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_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>')
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)
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)
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>')
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>', ]
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)