import io
import struct
from fontTools.misc.fixedTools import floatToFixed
from fontTools.misc.testTools import getXML
from fontTools.otlLib import builder, error
from fontTools import ttLib
from fontTools.ttLib.tables import otTables
import pytest
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) == [
'',
' ',
' ',
"",
]
def test_buildAnchor_format2(self):
anchor = builder.buildAnchor(23, 42, point=17)
assert getXML(anchor.toXML) == [
'',
' ',
' ',
' ',
"",
]
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) == [
'',
' ',
' ',
" ",
' ',
' ',
' ',
' ',
" ",
" ",
' ',
' ',
' ',
' ',
" ",
"",
]
def test_buildAttachList(self):
attachList = builder.buildAttachList(
{"zero": [23, 7], "one": [1]}, self.GLYPHMAP
)
assert getXML(attachList.toXML) == [
"",
" ",
' ',
' ',
" ",
" ",
' ',
" ",
' ',
' ',
" ",
' ',
" ",
' ',
" ",
"",
]
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) == [
"",
" ",
' ',
' ',
"",
]
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) == [
"",
" ",
' ',
' ',
"",
]
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) == [
"",
" ",
' ',
' ',
' ',
' ',
' ',
' ',
" ",
' ',
" ",
' ',
' ',
' ',
' ',
' ',
" ",
' ',
' ',
' ',
" ",
' ',
" ",
"",
]
def test_buildBaseRecord(self):
a = builder.buildAnchor
rec = builder.buildBaseRecord([a(500, -20), None, a(300, -15)])
assert getXML(rec.toXML) == [
"",
' ',
' ',
' ',
" ",
' ',
' ',
' ',
' ',
" ",
"",
]
def test_buildCaretValueForCoord(self):
caret = builder.buildCaretValueForCoord(500)
assert getXML(caret.toXML) == [
'',
' ',
"",
]
def test_buildCaretValueForPoint(self):
caret = builder.buildCaretValueForPoint(23)
assert getXML(caret.toXML) == [
'',
' ',
"",
]
def test_buildComponentRecord(self):
a = builder.buildAnchor
rec = builder.buildComponentRecord([a(500, -20), None, a(300, -15)])
assert getXML(rec.toXML) == [
"",
' ',
' ',
' ',
" ",
' ',
' ',
' ',
' ',
" ",
"",
]
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) == [
"",
' ',
' ',
"",
]
def test_buildCursivePos(self):
pos = builder.buildCursivePosSubtable(
{"two": (self.ANCHOR1, self.ANCHOR2), "four": (self.ANCHOR3, self.ANCHOR1)},
self.GLYPHMAP,
)
assert getXML(pos.toXML) == [
'',
" ",
' ',
' ',
" ",
" ",
' ',
' ',
' ',
' ',
" ",
' ',
' ',
' ',
" ",
" ",
' ',
' ',
' ',
' ',
" ",
' ',
' ',
' ',
" ",
" ",
"",
]
def test_buildDevice_format1(self):
device = builder.buildDevice({1: 1, 0: 0})
assert getXML(device.toXML) == [
"",
' ',
' ',
' ',
' ',
"",
]
def test_buildDevice_format2(self):
device = builder.buildDevice({2: 2, 0: 1, 1: 0})
assert getXML(device.toXML) == [
"",
' ',
' ',
' ',
' ',
"",
]
def test_buildDevice_format3(self):
device = builder.buildDevice({5: 3, 1: 77})
assert getXML(device.toXML) == [
"",
' ',
' ',
' ',
' ',
"",
]
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) == [
"",
" ",
' ', # f_i
" ",
' ',
' ',
' ',
' ',
' ',
' ',
" ",
' ',
" ",
' ',
' ',
' ',
' ',
' ',
" ",
" ",
' ',
" ",
' ',
' ',
' ',
' ',
' ',
" ",
' ',
' ',
' ',
' ',
' ',
" ",
' ',
' ',
' ',
" ",
' ',
" ",
" ",
"",
]
def test_buildLigatureAttach(self):
anchor = builder.buildAnchor
attach = builder.buildLigatureAttach(
[[anchor(500, -10), None], [None, anchor(300, -20), None]]
)
assert getXML(attach.toXML) == [
"",
" ",
' ',
' ',
' ',
' ',
" ",
' ',
" ",
' ',
' ',
' ',
' ',
' ',
" ",
' ',
" ",
"",
]
def test_buildLigatureAttach_emptyComponents(self):
attach = builder.buildLigatureAttach([[], None])
assert getXML(attach.toXML) == [
"",
" ",
' ',
' ',
"",
]
def test_buildLigatureAttach_noComponents(self):
attach = builder.buildLigatureAttach([])
assert getXML(attach.toXML) == [
"",
" ",
"",
]
def test_buildLigCaretList(self):
carets = builder.buildLigCaretList(
{"f_f_i": [300, 600]}, {"c_t": [42]}, self.GLYPHMAP
)
assert getXML(carets.toXML) == [
"",
" ",
' ',
' ',
" ",
" ",
' ',
" ",
' ',
' ',
" ",
' ',
' ',
" ",
" ",
' ',
" ",
' ',
' ',
" ",
" ",
"",
]
def test_buildLigCaretList_bothCoordsAndPointsForSameGlyph(self):
carets = builder.buildLigCaretList(
{"f_f_i": [300]}, {"f_f_i": [7]}, self.GLYPHMAP
)
assert getXML(carets.toXML) == [
"",
" ",
' ',
" ",
" ",
' ',
" ",
' ',
' ',
" ",
' ',
' ',
" ",
" ",
"",
]
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) == [
"",
" ",
' ',
' ',
" ",
' ',
' ',
" ",
"",
]
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) == [
"",
" ",
' ',
' ',
" ",
"",
]
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) == [
"",
' ',
' ',
" ",
' ',
' ',
" ",
' ',
' ',
" ",
"",
]
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)
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) == [
"",
' ',
' ',
" ",
' ',
' ',
" ",
' ',
"",
]
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) == [
"",
" ",
' ',
' ',
' ',
' ',
' ',
" ",
" ",
' ',
' ',
' ',
' ',
' ',
" ",
" ",
"",
]
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) == [
'',
" ",
' ',
' ',
' ',
" ",
" ",
' ',
' ',
' ',
' ',
' ',
" ",
" ",
" ",
" ",
' ', # grave
' ',
' ',
' ',
' ',
" ",
" ",
' ', # acute
' ',
' ',
' ',
' ',
" ",
" ",
' ', # cedilla
' ',
' ',
' ',
' ',
" ",
" ",
" ",
" ",
" ",
' ', # A
' ',
' ',
" ",
' ', # B
' ',
' ',
' ',
" ",
' ',
" ",
' ', # C
' ',
' ',
' ',
' ',
" ",
" ",
' ', # a
' ',
' ',
' ',
" ",
' ',
' ',
' ',
" ",
" ",
' ', # b
' ',
' ',
' ',
" ",
' ',
' ',
' ',
" ",
" ",
" ",
"",
]
def test_buildMarkGlyphSetsDef(self):
marksets = builder.buildMarkGlyphSetsDef(
[{"acute", "grave"}, {"cedilla", "grave"}], self.GLYPHMAP
)
assert getXML(marksets.toXML) == [
"",
' ',
" ",
' ',
' ',
' ',
" ",
' ',
' ',
' ',
" ",
"",
]
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) == [
'',
" ",
' ',
' ',
' ',
" ",
" ",
' ',
' ',
" ",
" ",
" ",
" ",
' ',
' ',
' ',
' ',
' ',
" ",
" ",
' ',
' ',
' ',
' ',
' ',
" ",
" ",
' ',
' ',
' ',
' ',
' ',
" ",
" ",
" ",
" ",
" ",
' ',
" ",
' ',
' ',
' ',
" ",
' ',
' ',
' ',
' ',
" ",
' ',
" ",
" ",
' ',
" ",
' ',
' ',
' ',
' ',
" ",
' ',
' ',
' ',
" ",
" ",
' ',
' ',
' ',
' ',
" ",
' ',
' ',
' ',
" ",
" ",
" ",
" ",
"",
]
def test_buildMarkRecord(self):
rec = builder.buildMarkRecord(17, builder.buildAnchor(500, -20))
assert getXML(rec.toXML) == [
"",
' ',
' ',
' ',
' ',
" ",
"",
]
def test_buildMark2Record(self):
a = builder.buildAnchor
rec = builder.buildMark2Record([a(500, -20), None, a(300, -15)])
assert getXML(rec.toXML) == [
"",
' ',
' ',
' ',
" ",
' ',
' ',
' ',
' ',
" ",
"",
]
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) == [
'',
" ",
' ',
' ',
' ',
" ",
' ',
' ',
" ",
' ',
" ",
" ",
' ',
' ',
' ',
" ",
" ",
" ",
' ',
' ',
' ',
' ',
" ",
' ',
' ',
' ',
" ",
' ',
' ',
' ',
" ",
" ",
' ',
' ',
' ',
' ',
" ",
' ',
' ',
' ',
" ",
' ',
' ',
' ',
" ",
" ",
"",
]
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], []) == [
'',
" ",
' ',
" ",
' ',
' ',
" ",
' ',
" ",
' ',
' ',
' ',
" ",
" ",
"",
'',
" ",
' ',
" ",
' ',
' ',
" ",
' ',
" ",
' ',
' ',
' ',
' ',
" ",
" ",
"",
]
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) == [
'',
" ",
' ',
' ',
" ",
' ',
' ',
" ",
' ',
" ",
' ',
' ',
' ',
' ',
" ",
' ',
' ',
' ',
' ',
" ",
" ",
' ',
" ",
' ',
' ',
' ',
' ',
" ",
" ",
"",
]
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], []) == [
'',
" ",
' ',
' ',
' ',
' ',
' ',
" ",
' ',
" ",
' ',
' ',
' ',
' ',
' ',
"",
'',
" ",
' ',
" ",
' ',
' ',
"",
]
def test_buildSinglePos_ValueFormat0(self):
subtables = builder.buildSinglePos(
{"zero": builder.buildValue({})}, self.GLYPHMAP
)
assert sum([getXML(t.toXML) for t in subtables], []) == [
'',
" ",
' ',
" ",
' ',
"",
]
def test_buildSinglePosSubtable_format1(self):
subtable = builder.buildSinglePosSubtable(
{
"one": builder.buildValue({"XPlacement": 777}),
"two": builder.buildValue({"XPlacement": 777}),
},
self.GLYPHMAP,
)
assert getXML(subtable.toXML) == [
'',
" ",
' ',
' ',
" ",
' ',
' ',
"",
]
def test_buildSinglePosSubtable_format2(self):
subtable = builder.buildSinglePosSubtable(
{
"one": builder.buildValue({"XPlacement": 777}),
"two": builder.buildValue({"YPlacement": -888}),
},
self.GLYPHMAP,
)
assert getXML(subtable.toXML) == [
'',
" ",
' ',
' ',
" ",
' ',
" ",
' ',
' ',
"",
]
def test_buildValue(self):
value = builder.buildValue({"XPlacement": 7, "YPlacement": 23})
func = lambda writer, font: value.toXML(writer, font, valueName="Val")
assert getXML(func) == ['']
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)
class ClassDefBuilderTest(object):
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()
assert isinstance(cdef, otTables.ClassDef)
assert 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()
assert isinstance(cdef, otTables.ClassDef)
assert 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"})
assert b.canAdd({"a", "b", "c", "d"})
assert b.canAdd({"e", "f"})
assert b.canAdd({"g", "h", "i"})
assert not b.canAdd({"b", "c", "d"})
assert not b.canAdd({"a", "b", "c", "d", "e", "f"})
assert not b.canAdd({"d", "e", "f"})
assert not b.canAdd({"f"})
def test_add_exception(self):
b = builder.ClassDefBuilder(useClass0=True)
b.add({"a", "b", "c"})
with pytest.raises(error.OpenTypeLibError):
b.add({"a", "d"})
buildStatTable_test_data = [
([
dict(
tag="wght",
name="Weight",
values=[
dict(value=100, name='Thin'),
dict(value=400, name='Regular', flags=0x2),
dict(value=900, name='Black')])], None, "Regular", [
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ']),
([
dict(
tag="wght",
name=dict(en="Weight", nl="Gewicht"),
values=[
dict(value=100, name=dict(en='Thin', nl='Dun')),
dict(value=400, name='Regular', flags=0x2),
dict(value=900, name='Black'),
]),
dict(
tag="wdth",
name="Width",
values=[
dict(value=50, name='Condensed'),
dict(value=100, name='Regular', flags=0x2),
dict(value=200, name='Extended')])], None, 2, [
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ']),
([
dict(
tag="wght",
name="Weight",
values=[
dict(value=400, name='Regular', flags=0x2),
dict(value=600, linkedValue=650, name='Bold')])], None, 18, [
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ']),
([
dict(
tag="opsz",
name="Optical Size",
values=[
dict(nominalValue=6, rangeMaxValue=10, name='Small'),
dict(rangeMinValue=10, nominalValue=14, rangeMaxValue=24, name='Text', flags=0x2),
dict(rangeMinValue=24, nominalValue=600, name='Display')])], None, 2, [
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ']),
([
dict(
tag="wght",
name="Weight",
ordering=1,
values=[]),
dict(
tag="ABCD",
name="ABCDTest",
ordering=0,
values=[
dict(value=100, name="Regular", flags=0x2)])],
[dict(location=dict(wght=300, ABCD=100), name='Regular ABCD')], 18, [
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ']),
]
@pytest.mark.parametrize("axes, axisValues, elidedFallbackName, expected_ttx", buildStatTable_test_data)
def test_buildStatTable(axes, axisValues, elidedFallbackName, expected_ttx):
font = ttLib.TTFont()
font["name"] = ttLib.newTable("name")
font["name"].names = []
# https://github.com/fonttools/fonttools/issues/1985
# Add nameID < 256 that matches a test axis name, to test whether
# the nameID is not reused: AxisNameIDs must be > 255 according
# to the spec.
font["name"].addMultilingualName(dict(en="ABCDTest"), nameID=6)
builder.buildStatTable(font, axes, axisValues, elidedFallbackName)
f = io.StringIO()
font.saveXML(f, tables=["STAT"])
ttx = f.getvalue().splitlines()
ttx = ttx[3:-2] # strip XML header and element
assert expected_ttx == ttx
# Compile and round-trip
f = io.BytesIO()
font.save(f)
font = ttLib.TTFont(f)
f = io.StringIO()
font.saveXML(f, tables=["STAT"])
ttx = f.getvalue().splitlines()
ttx = ttx[3:-2] # strip XML header and element
assert expected_ttx == ttx
def test_stat_infinities():
negInf = floatToFixed(builder.AXIS_VALUE_NEGATIVE_INFINITY, 16)
assert struct.pack(">l", negInf) == b"\x80\x00\x00\x00"
posInf = floatToFixed(builder.AXIS_VALUE_POSITIVE_INFINITY, 16)
assert struct.pack(">l", posInf) == b"\x7f\xff\xff\xff"
class ChainContextualRulesetTest(object):
def test_makeRulesets(self):
font = ttLib.TTFont()
font.setGlyphOrder(["a","b","c","d","A","B","C","D","E"])
sb = builder.ChainContextSubstBuilder(font, None)
prefix, input_, suffix, lookups = [["a"], ["b"]], [["c"]], [], [None]
sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups))
prefix, input_, suffix, lookups = [["a"], ["d"]], [["c"]], [], [None]
sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups))
sb.add_subtable_break(None)
# Second subtable has some glyph classes
prefix, input_, suffix, lookups = [["A"]], [["E"]], [], [None]
sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups))
prefix, input_, suffix, lookups = [["A"]], [["C","D"]], [], [None]
sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups))
prefix, input_, suffix, lookups = [["A", "B"]], [["E"]], [], [None]
sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups))
sb.add_subtable_break(None)
# Third subtable has no pre/post context
prefix, input_, suffix, lookups = [], [["E"]], [], [None]
sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups))
prefix, input_, suffix, lookups = [], [["C","D"]], [], [None]
sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups))
rulesets = sb.rulesets()
assert len(rulesets) == 3
assert rulesets[0].hasPrefixOrSuffix
assert not rulesets[0].hasAnyGlyphClasses
cd = rulesets[0].format2ClassDefs()
assert set(cd[0].classes()[1:]) == set([("d",),("b",),("a",)])
assert set(cd[1].classes()[1:]) == set([("c",)])
assert set(cd[2].classes()[1:]) == set()
assert rulesets[1].hasPrefixOrSuffix
assert rulesets[1].hasAnyGlyphClasses
assert not rulesets[1].format2ClassDefs()
assert not rulesets[2].hasPrefixOrSuffix
assert rulesets[2].hasAnyGlyphClasses
assert rulesets[2].format2ClassDefs()
cd = rulesets[2].format2ClassDefs()
assert set(cd[0].classes()[1:]) == set()
assert set(cd[1].classes()[1:]) == set([("C","D"), ("E",)])
assert set(cd[2].classes()[1:]) == set()
if __name__ == "__main__":
import sys
sys.exit(pytest.main(sys.argv))