from __future__ import print_function, division, absolute_import
from __future__ import unicode_literals
from fontTools.misc.testTools import getXML
from fontTools.otlLib import builder
from fontTools.ttLib.tables import otTables
from itertools import chain
import unittest
class BuilderTest(unittest.TestCase):
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 __init__(self, methodName):
unittest.TestCase.__init__(self, methodName)
# Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
# and fires deprecation warnings if a program uses the old name.
if not hasattr(self, "assertRaisesRegex"):
self.assertRaisesRegex = self.assertRaisesRegexp
@classmethod
def setUpClass(cls):
cls.maxDiff = None
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 test_buildAnchor_format2(self):
anchor = builder.buildAnchor(23, 42, point=17)
self.assertEqual(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}))
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_buildAttachList(self):
attachList = builder.buildAttachList({
"zero": [23, 7], "one": [1],
}, self.GLYPHMAP)
self.assertEqual(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):
self.assertIsNone(builder.buildAttachList({}, self.GLYPHMAP))
def test_buildAttachPoint(self):
attachPoint = builder.buildAttachPoint([7, 3])
self.assertEqual(getXML(attachPoint.toXML),
['<AttachPoint>',
' <!-- PointCount=2 -->',
' <PointIndex index="0" value="3"/>',
' <PointIndex index="1" value="7"/>',
'</AttachPoint>'])
def test_buildAttachPoint_empty(self):
self.assertIsNone(builder.buildAttachPoint([]))
def test_buildAttachPoint_duplicate(self):
attachPoint = builder.buildAttachPoint([7, 3, 7])
self.assertEqual(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)
self.assertEqual(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)])
self.assertEqual(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)
self.assertEqual(getXML(caret.toXML),
['<CaretValue Format="1">',
' <Coordinate value="500"/>',
'</CaretValue>'])
def test_buildCaretValueForPoint(self):
caret = builder.buildCaretValueForPoint(23)
self.assertEqual(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)])
self.assertEqual(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):
self.assertIsNone(builder.buildComponentRecord([]))
def test_buildComponentRecord_None(self):
self.assertIsNone(builder.buildComponentRecord(None))
def test_buildCoverage(self):
cov = builder.buildCoverage({"two", "four"}, {"two": 2, "four": 4})
self.assertEqual(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)
self.assertEqual(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})
self.assertEqual(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})
self.assertEqual(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})
self.assertEqual(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)
self.assertEqual(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]])
self.assertEqual(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])
self.assertEqual(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([])
self.assertEqual(getXML(attach.toXML),
['<LigatureAttach>',
' <!-- ComponentCount=0 -->',
'</LigatureAttach>'])
def test_buildLigCaretList(self):
carets = builder.buildLigCaretList(
{"f_f_i": [300, 600]}, {"c_t": [42]}, self.GLYPHMAP)
self.assertEqual(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)
self.assertEqual(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):
self.assertIsNone(builder.buildLigCaretList({}, {}, self.GLYPHMAP))
def test_buildLigCaretList_None(self):
self.assertIsNone(builder.buildLigCaretList(None, None, self.GLYPHMAP))
def test_buildLigGlyph_coords(self):
lig = builder.buildLigGlyph([500, 800], None)
self.assertEqual(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):
self.assertIsNone(builder.buildLigGlyph([], []))
def test_buildLigGlyph_None(self):
self.assertIsNone(builder.buildLigGlyph(None, None))
def test_buildLigGlyph_points(self):
lig = builder.buildLigGlyph(None, [2])
self.assertEqual(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)
self.assertEqual(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"})
self.assertRaisesRegex(
AssertionError, "if markFilterSet is None, "
"flags must not set LOOKUP_FLAG_USE_MARK_FILTERING_SET; "
"flags=0x0010",
builder.buildLookup, [s],
builder.LOOKUP_FLAG_USE_MARK_FILTERING_SET, None)
self.assertRaisesRegex(
AssertionError, "if markFilterSet is not None, "
"flags must set LOOKUP_FLAG_USE_MARK_FILTERING_SET; "
"flags=0x0004",
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"]})
self.assertRaisesRegex(
AssertionError, "all subtables must have the same LookupType",
builder.buildLookup, [s1, s2])
def test_buildLookup_noSubtables(self):
self.assertIsNone(builder.buildLookup([]))
self.assertIsNone(builder.buildLookup(None))
self.assertIsNone(builder.buildLookup([None]))
self.assertIsNone(builder.buildLookup([None, 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)
self.assertEqual(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)
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_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)
self.assertEqual(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)
self.assertEqual(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):
self.assertIsNone(builder.buildMarkGlyphSetsDef([], self.GLYPHMAP))
def test_buildMarkGlyphSetsDef_None(self):
self.assertIsNone(builder.buildMarkGlyphSetsDef(None, self.GLYPHMAP))
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)
self.assertEqual(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))
self.assertEqual(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)])
self.assertEqual(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)
self.assertEqual(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)
self.assertEqual(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)
self.assertEqual(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)
self.assertEqual(sum([getXML(t.toXML) for t in subtables], []),
['<SinglePos Format="1">',
' <Coverage>',
' <Glyph value="one"/>',
' <Glyph value="two"/>',
' <Glyph value="five"/>',
' </Coverage>',
' <ValueFormat value="1"/>',
' <Value XPlacement="500"/>',
'</SinglePos>',
'<SinglePos Format="2">',
' <Coverage>',
' <Glyph value="three"/>',
' <Glyph value="four"/>',
' </Coverage>',
' <ValueFormat value="1"/>',
' <!-- ValueCount=2 -->',
' <Value index="0" XPlacement="200"/>',
' <Value index="1" XPlacement="400"/>',
'</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)
self.assertEqual(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)
self.assertEqual(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)
self.assertEqual(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")
self.assertEqual(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)
self.assertEqual(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)
self.assertEqual(keyA1, keyA2)
self.assertEqual(hash(keyA1), hash(keyA2))
self.assertNotEqual(keyA1, keyB)
self.assertNotEqual(hash(keyA1), hash(keyB))
class ClassDefBuilderTest(unittest.TestCase):
def test_build_usingClass0(self):
b = builder.ClassDefBuilder(useClass0=True)
b.add({"aa", "bb"})
b.add({"a", "b"})
b.add({"c"})
b.add({"e", "f", "g", "h"})
cdef = b.build()
self.assertIsInstance(cdef, otTables.ClassDef)
self.assertEqual(cdef.classDefs, {
"a": 2,
"b": 2,
"c": 3,
"aa": 1,
"bb": 1
})
def test_build_notUsingClass0(self):
b = builder.ClassDefBuilder(useClass0=False)
b.add({"a", "b"})
b.add({"c"})
b.add({"e", "f", "g", "h"})
cdef = b.build()
self.assertIsInstance(cdef, otTables.ClassDef)
self.assertEqual(cdef.classDefs, {
"a": 2,
"b": 2,
"c": 3,
"e": 1,
"f": 1,
"g": 1,
"h": 1
})
def test_canAdd(self):
b = builder.ClassDefBuilder(useClass0=True)
b.add({"a", "b", "c", "d"})
b.add({"e", "f"})
self.assertTrue(b.canAdd({"a", "b", "c", "d"}))
self.assertTrue(b.canAdd({"e", "f"}))
self.assertTrue(b.canAdd({"g", "h", "i"}))
self.assertFalse(b.canAdd({"b", "c", "d"}))
self.assertFalse(b.canAdd({"a", "b", "c", "d", "e", "f"}))
self.assertFalse(b.canAdd({"d", "e", "f"}))
self.assertFalse(b.canAdd({"f"}))
if __name__ == "__main__":
import sys
sys.exit(unittest.main())